eac-rack 1.1.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.
Files changed (111) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +399 -0
  4. data/bin/rackup +4 -0
  5. data/contrib/rack_logo.svg +111 -0
  6. data/example/lobster.ru +4 -0
  7. data/example/protectedlobster.rb +14 -0
  8. data/example/protectedlobster.ru +8 -0
  9. data/lib/rack.rb +92 -0
  10. data/lib/rack/adapter/camping.rb +22 -0
  11. data/lib/rack/auth/abstract/handler.rb +37 -0
  12. data/lib/rack/auth/abstract/request.rb +37 -0
  13. data/lib/rack/auth/basic.rb +58 -0
  14. data/lib/rack/auth/digest/md5.rb +124 -0
  15. data/lib/rack/auth/digest/nonce.rb +51 -0
  16. data/lib/rack/auth/digest/params.rb +55 -0
  17. data/lib/rack/auth/digest/request.rb +40 -0
  18. data/lib/rack/builder.rb +80 -0
  19. data/lib/rack/cascade.rb +41 -0
  20. data/lib/rack/chunked.rb +49 -0
  21. data/lib/rack/commonlogger.rb +49 -0
  22. data/lib/rack/conditionalget.rb +47 -0
  23. data/lib/rack/config.rb +15 -0
  24. data/lib/rack/content_length.rb +29 -0
  25. data/lib/rack/content_type.rb +23 -0
  26. data/lib/rack/deflater.rb +96 -0
  27. data/lib/rack/directory.rb +157 -0
  28. data/lib/rack/etag.rb +23 -0
  29. data/lib/rack/file.rb +90 -0
  30. data/lib/rack/handler.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +89 -0
  34. data/lib/rack/handler/lsws.rb +63 -0
  35. data/lib/rack/handler/mongrel.rb +90 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +69 -0
  40. data/lib/rack/head.rb +19 -0
  41. data/lib/rack/lint.rb +575 -0
  42. data/lib/rack/lobster.rb +65 -0
  43. data/lib/rack/lock.rb +16 -0
  44. data/lib/rack/logger.rb +20 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +206 -0
  47. data/lib/rack/mock.rb +189 -0
  48. data/lib/rack/nulllogger.rb +18 -0
  49. data/lib/rack/recursive.rb +57 -0
  50. data/lib/rack/reloader.rb +109 -0
  51. data/lib/rack/request.rb +271 -0
  52. data/lib/rack/response.rb +149 -0
  53. data/lib/rack/rewindable_input.rb +100 -0
  54. data/lib/rack/runtime.rb +27 -0
  55. data/lib/rack/sendfile.rb +142 -0
  56. data/lib/rack/server.rb +212 -0
  57. data/lib/rack/session/abstract/id.rb +140 -0
  58. data/lib/rack/session/cookie.rb +90 -0
  59. data/lib/rack/session/memcache.rb +119 -0
  60. data/lib/rack/session/pool.rb +100 -0
  61. data/lib/rack/showexceptions.rb +349 -0
  62. data/lib/rack/showstatus.rb +106 -0
  63. data/lib/rack/static.rb +38 -0
  64. data/lib/rack/urlmap.rb +56 -0
  65. data/lib/rack/utils.rb +614 -0
  66. data/rack.gemspec +38 -0
  67. data/test/spec_rack_auth_basic.rb +73 -0
  68. data/test/spec_rack_auth_digest.rb +226 -0
  69. data/test/spec_rack_builder.rb +84 -0
  70. data/test/spec_rack_camping.rb +51 -0
  71. data/test/spec_rack_cascade.rb +48 -0
  72. data/test/spec_rack_cgi.rb +89 -0
  73. data/test/spec_rack_chunked.rb +62 -0
  74. data/test/spec_rack_commonlogger.rb +61 -0
  75. data/test/spec_rack_conditionalget.rb +41 -0
  76. data/test/spec_rack_config.rb +24 -0
  77. data/test/spec_rack_content_length.rb +43 -0
  78. data/test/spec_rack_content_type.rb +30 -0
  79. data/test/spec_rack_deflater.rb +127 -0
  80. data/test/spec_rack_directory.rb +61 -0
  81. data/test/spec_rack_etag.rb +17 -0
  82. data/test/spec_rack_fastcgi.rb +89 -0
  83. data/test/spec_rack_file.rb +75 -0
  84. data/test/spec_rack_handler.rb +43 -0
  85. data/test/spec_rack_head.rb +30 -0
  86. data/test/spec_rack_lint.rb +528 -0
  87. data/test/spec_rack_lobster.rb +45 -0
  88. data/test/spec_rack_lock.rb +38 -0
  89. data/test/spec_rack_logger.rb +21 -0
  90. data/test/spec_rack_methodoverride.rb +60 -0
  91. data/test/spec_rack_mock.rb +243 -0
  92. data/test/spec_rack_mongrel.rb +189 -0
  93. data/test/spec_rack_nulllogger.rb +13 -0
  94. data/test/spec_rack_recursive.rb +77 -0
  95. data/test/spec_rack_request.rb +545 -0
  96. data/test/spec_rack_response.rb +221 -0
  97. data/test/spec_rack_rewindable_input.rb +118 -0
  98. data/test/spec_rack_runtime.rb +35 -0
  99. data/test/spec_rack_sendfile.rb +86 -0
  100. data/test/spec_rack_session_cookie.rb +73 -0
  101. data/test/spec_rack_session_memcache.rb +273 -0
  102. data/test/spec_rack_session_pool.rb +172 -0
  103. data/test/spec_rack_showexceptions.rb +21 -0
  104. data/test/spec_rack_showstatus.rb +72 -0
  105. data/test/spec_rack_static.rb +37 -0
  106. data/test/spec_rack_thin.rb +91 -0
  107. data/test/spec_rack_urlmap.rb +215 -0
  108. data/test/spec_rack_utils.rb +554 -0
  109. data/test/spec_rack_webrick.rb +130 -0
  110. data/test/spec_rackup.rb +154 -0
  111. metadata +311 -0
@@ -0,0 +1,21 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/showexceptions'
4
+ require 'rack/mock'
5
+
6
+ context "Rack::ShowExceptions" do
7
+ specify "catches exceptions" do
8
+ res = nil
9
+ req = Rack::MockRequest.new(Rack::ShowExceptions.new(lambda { |env|
10
+ raise RuntimeError
11
+ }))
12
+ lambda {
13
+ res = req.get("/")
14
+ }.should.not.raise
15
+ res.should.be.a.server_error
16
+ res.status.should.equal 500
17
+
18
+ res.should =~ /RuntimeError/
19
+ res.should =~ /ShowExceptions/
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/showstatus'
4
+ require 'rack/mock'
5
+
6
+ context "Rack::ShowStatus" do
7
+ specify "should provide a default status message" do
8
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
9
+ [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
10
+ }))
11
+
12
+ res = req.get("/", :lint => true)
13
+ res.should.be.not_found
14
+ res.should.be.not.empty
15
+
16
+ res["Content-Type"].should.equal("text/html")
17
+ res.should =~ /404/
18
+ res.should =~ /Not Found/
19
+ end
20
+
21
+ specify "should let the app provide additional information" do
22
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
23
+ env["rack.showstatus.detail"] = "gone too meta."
24
+ [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
25
+ }))
26
+
27
+ res = req.get("/", :lint => true)
28
+ res.should.be.not_found
29
+ res.should.be.not.empty
30
+
31
+ res["Content-Type"].should.equal("text/html")
32
+ res.should =~ /404/
33
+ res.should =~ /Not Found/
34
+ res.should =~ /too meta/
35
+ end
36
+
37
+ specify "should not replace existing messages" do
38
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
39
+ [404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]]
40
+ }))
41
+ res = req.get("/", :lint => true)
42
+ res.should.be.not_found
43
+
44
+ res.body.should == "foo!"
45
+ end
46
+
47
+ specify "should pass on original headers" do
48
+ headers = {"WWW-Authenticate" => "Basic blah"}
49
+
50
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| [401, headers, []] }))
51
+ res = req.get("/", :lint => true)
52
+
53
+ res["WWW-Authenticate"].should.equal("Basic blah")
54
+ end
55
+
56
+ specify "should replace existing messages if there is detail" do
57
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
58
+ env["rack.showstatus.detail"] = "gone too meta."
59
+ [404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]]
60
+ }))
61
+
62
+ res = req.get("/", :lint => true)
63
+ res.should.be.not_found
64
+ res.should.be.not.empty
65
+
66
+ res["Content-Type"].should.equal("text/html")
67
+ res["Content-Length"].should.not.equal("4")
68
+ res.should =~ /404/
69
+ res.should =~ /too meta/
70
+ res.body.should.not =~ /foo/
71
+ end
72
+ end
@@ -0,0 +1,37 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/static'
4
+ require 'rack/mock'
5
+
6
+ class DummyApp
7
+ def call(env)
8
+ [200, {}, ["Hello World"]]
9
+ end
10
+ end
11
+
12
+ context "Rack::Static" do
13
+ root = File.expand_path(File.dirname(__FILE__))
14
+ OPTIONS = {:urls => ["/cgi"], :root => root}
15
+
16
+ setup do
17
+ @request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, OPTIONS))
18
+ end
19
+
20
+ specify "serves files" do
21
+ res = @request.get("/cgi/test")
22
+ res.should.be.ok
23
+ res.body.should =~ /ruby/
24
+ end
25
+
26
+ specify "404s if url root is known but it can't find the file" do
27
+ res = @request.get("/cgi/foo")
28
+ res.should.be.not_found
29
+ end
30
+
31
+ specify "calls down the chain if url root is not known" do
32
+ res = @request.get("/something/else")
33
+ res.should.be.ok
34
+ res.body.should == "Hello World"
35
+ end
36
+
37
+ end
@@ -0,0 +1,91 @@
1
+ require 'test/spec'
2
+
3
+ begin
4
+ require 'rack/handler/thin'
5
+ require 'testrequest'
6
+ require 'timeout'
7
+
8
+ context "Rack::Handler::Thin" do
9
+ include TestRequest::Helpers
10
+
11
+ setup do
12
+ @app = Rack::Lint.new(TestRequest.new)
13
+ @server = nil
14
+ Thin::Logging.silent = true
15
+ @thread = Thread.new do
16
+ Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server|
17
+ @server = server
18
+ end
19
+ end
20
+ Thread.pass until @server && @server.running?
21
+ end
22
+
23
+ specify "should respond" do
24
+ lambda {
25
+ GET("/")
26
+ }.should.not.raise
27
+ end
28
+
29
+ specify "should be a Thin" do
30
+ GET("/")
31
+ status.should.be 200
32
+ response["SERVER_SOFTWARE"].should =~ /thin/
33
+ response["HTTP_VERSION"].should.equal "HTTP/1.1"
34
+ response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
35
+ response["SERVER_PORT"].should.equal "9204"
36
+ response["SERVER_NAME"].should.equal "0.0.0.0"
37
+ end
38
+
39
+ specify "should have rack headers" do
40
+ GET("/")
41
+ response["rack.version"].should.equal [0,3]
42
+ response["rack.multithread"].should.be false
43
+ response["rack.multiprocess"].should.be false
44
+ response["rack.run_once"].should.be false
45
+ end
46
+
47
+ specify "should have CGI headers on GET" do
48
+ GET("/")
49
+ response["REQUEST_METHOD"].should.equal "GET"
50
+ response["REQUEST_PATH"].should.equal "/"
51
+ response["PATH_INFO"].should.be.equal "/"
52
+ response["QUERY_STRING"].should.equal ""
53
+ response["test.postdata"].should.equal ""
54
+
55
+ GET("/test/foo?quux=1")
56
+ response["REQUEST_METHOD"].should.equal "GET"
57
+ response["REQUEST_PATH"].should.equal "/test/foo"
58
+ response["PATH_INFO"].should.equal "/test/foo"
59
+ response["QUERY_STRING"].should.equal "quux=1"
60
+ end
61
+
62
+ specify "should have CGI headers on POST" do
63
+ POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
64
+ status.should.equal 200
65
+ response["REQUEST_METHOD"].should.equal "POST"
66
+ response["REQUEST_PATH"].should.equal "/"
67
+ response["QUERY_STRING"].should.equal ""
68
+ response["HTTP_X_TEST_HEADER"].should.equal "42"
69
+ response["test.postdata"].should.equal "rack-form-data=23"
70
+ end
71
+
72
+ specify "should support HTTP auth" do
73
+ GET("/test", {:user => "ruth", :passwd => "secret"})
74
+ response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
75
+ end
76
+
77
+ specify "should set status" do
78
+ GET("/test?secret")
79
+ status.should.equal 403
80
+ response["rack.url_scheme"].should.equal "http"
81
+ end
82
+
83
+ teardown do
84
+ @server.stop!
85
+ @thread.kill
86
+ end
87
+ end
88
+
89
+ rescue LoadError
90
+ $stderr.puts "Skipping Rack::Handler::Thin tests (Thin is required). `gem install thin` and try again."
91
+ end
@@ -0,0 +1,215 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/urlmap'
4
+ require 'rack/mock'
5
+
6
+ context "Rack::URLMap" do
7
+ specify "dispatches paths correctly" do
8
+ app = lambda { |env|
9
+ [200, {
10
+ 'X-ScriptName' => env['SCRIPT_NAME'],
11
+ 'X-PathInfo' => env['PATH_INFO'],
12
+ 'Content-Type' => 'text/plain'
13
+ }, [""]]
14
+ }
15
+ map = Rack::URLMap.new({
16
+ 'http://foo.org/bar' => app,
17
+ '/foo' => app,
18
+ '/foo/bar' => app
19
+ })
20
+
21
+ res = Rack::MockRequest.new(map).get("/")
22
+ res.should.be.not_found
23
+
24
+ res = Rack::MockRequest.new(map).get("/qux")
25
+ res.should.be.not_found
26
+
27
+ res = Rack::MockRequest.new(map).get("/foo")
28
+ res.should.be.ok
29
+ res["X-ScriptName"].should.equal "/foo"
30
+ res["X-PathInfo"].should.equal ""
31
+
32
+ res = Rack::MockRequest.new(map).get("/foo/")
33
+ res.should.be.ok
34
+ res["X-ScriptName"].should.equal "/foo"
35
+ res["X-PathInfo"].should.equal "/"
36
+
37
+ res = Rack::MockRequest.new(map).get("/foo/bar")
38
+ res.should.be.ok
39
+ res["X-ScriptName"].should.equal "/foo/bar"
40
+ res["X-PathInfo"].should.equal ""
41
+
42
+ res = Rack::MockRequest.new(map).get("/foo/bar/")
43
+ res.should.be.ok
44
+ res["X-ScriptName"].should.equal "/foo/bar"
45
+ res["X-PathInfo"].should.equal "/"
46
+
47
+ res = Rack::MockRequest.new(map).get("/foo///bar//quux")
48
+ res.status.should.equal 200
49
+ res.should.be.ok
50
+ res["X-ScriptName"].should.equal "/foo/bar"
51
+ res["X-PathInfo"].should.equal "//quux"
52
+
53
+ res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh")
54
+ res.should.be.ok
55
+ res["X-ScriptName"].should.equal "/bleh/foo"
56
+ res["X-PathInfo"].should.equal "/quux"
57
+
58
+ res = Rack::MockRequest.new(map).get("/bar", 'HTTP_HOST' => 'foo.org')
59
+ res.should.be.ok
60
+ res["X-ScriptName"].should.equal "/bar"
61
+ res["X-PathInfo"].should.be.empty
62
+
63
+ res = Rack::MockRequest.new(map).get("/bar/", 'HTTP_HOST' => 'foo.org')
64
+ res.should.be.ok
65
+ res["X-ScriptName"].should.equal "/bar"
66
+ res["X-PathInfo"].should.equal '/'
67
+ end
68
+
69
+
70
+ specify "dispatches hosts correctly" do
71
+ map = Rack::URLMap.new("http://foo.org/" => lambda { |env|
72
+ [200,
73
+ { "Content-Type" => "text/plain",
74
+ "X-Position" => "foo.org",
75
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
76
+ }, [""]]},
77
+ "http://subdomain.foo.org/" => lambda { |env|
78
+ [200,
79
+ { "Content-Type" => "text/plain",
80
+ "X-Position" => "subdomain.foo.org",
81
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
82
+ }, [""]]},
83
+ "http://bar.org/" => lambda { |env|
84
+ [200,
85
+ { "Content-Type" => "text/plain",
86
+ "X-Position" => "bar.org",
87
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
88
+ }, [""]]},
89
+ "/" => lambda { |env|
90
+ [200,
91
+ { "Content-Type" => "text/plain",
92
+ "X-Position" => "default.org",
93
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
94
+ }, [""]]}
95
+ )
96
+
97
+ res = Rack::MockRequest.new(map).get("/")
98
+ res.should.be.ok
99
+ res["X-Position"].should.equal "default.org"
100
+
101
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "bar.org")
102
+ res.should.be.ok
103
+ res["X-Position"].should.equal "bar.org"
104
+
105
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "foo.org")
106
+ res.should.be.ok
107
+ res["X-Position"].should.equal "foo.org"
108
+
109
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "subdomain.foo.org", "SERVER_NAME" => "foo.org")
110
+ res.should.be.ok
111
+ res["X-Position"].should.equal "subdomain.foo.org"
112
+
113
+ res = Rack::MockRequest.new(map).get("http://foo.org/")
114
+ res.should.be.ok
115
+ res["X-Position"].should.equal "default.org"
116
+
117
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org")
118
+ res.should.be.ok
119
+ res["X-Position"].should.equal "default.org"
120
+
121
+ res = Rack::MockRequest.new(map).get("/",
122
+ "HTTP_HOST" => "example.org:9292",
123
+ "SERVER_PORT" => "9292")
124
+ res.should.be.ok
125
+ res["X-Position"].should.equal "default.org"
126
+ end
127
+
128
+ specify "should be nestable" do
129
+ map = Rack::URLMap.new("/foo" =>
130
+ Rack::URLMap.new("/bar" =>
131
+ Rack::URLMap.new("/quux" => lambda { |env|
132
+ [200,
133
+ { "Content-Type" => "text/plain",
134
+ "X-Position" => "/foo/bar/quux",
135
+ "X-PathInfo" => env["PATH_INFO"],
136
+ "X-ScriptName" => env["SCRIPT_NAME"],
137
+ }, [""]]}
138
+ )))
139
+
140
+ res = Rack::MockRequest.new(map).get("/foo/bar")
141
+ res.should.be.not_found
142
+
143
+ res = Rack::MockRequest.new(map).get("/foo/bar/quux")
144
+ res.should.be.ok
145
+ res["X-Position"].should.equal "/foo/bar/quux"
146
+ res["X-PathInfo"].should.equal ""
147
+ res["X-ScriptName"].should.equal "/foo/bar/quux"
148
+ end
149
+
150
+ specify "should route root apps correctly" do
151
+ map = Rack::URLMap.new("/" => lambda { |env|
152
+ [200,
153
+ { "Content-Type" => "text/plain",
154
+ "X-Position" => "root",
155
+ "X-PathInfo" => env["PATH_INFO"],
156
+ "X-ScriptName" => env["SCRIPT_NAME"]
157
+ }, [""]]},
158
+ "/foo" => lambda { |env|
159
+ [200,
160
+ { "Content-Type" => "text/plain",
161
+ "X-Position" => "foo",
162
+ "X-PathInfo" => env["PATH_INFO"],
163
+ "X-ScriptName" => env["SCRIPT_NAME"]
164
+ }, [""]]}
165
+ )
166
+
167
+ res = Rack::MockRequest.new(map).get("/foo/bar")
168
+ res.should.be.ok
169
+ res["X-Position"].should.equal "foo"
170
+ res["X-PathInfo"].should.equal "/bar"
171
+ res["X-ScriptName"].should.equal "/foo"
172
+
173
+ res = Rack::MockRequest.new(map).get("/foo")
174
+ res.should.be.ok
175
+ res["X-Position"].should.equal "foo"
176
+ res["X-PathInfo"].should.equal ""
177
+ res["X-ScriptName"].should.equal "/foo"
178
+
179
+ res = Rack::MockRequest.new(map).get("/bar")
180
+ res.should.be.ok
181
+ res["X-Position"].should.equal "root"
182
+ res["X-PathInfo"].should.equal "/bar"
183
+ res["X-ScriptName"].should.equal ""
184
+
185
+ res = Rack::MockRequest.new(map).get("")
186
+ res.should.be.ok
187
+ res["X-Position"].should.equal "root"
188
+ res["X-PathInfo"].should.equal "/"
189
+ res["X-ScriptName"].should.equal ""
190
+ end
191
+
192
+ specify "should not squeeze slashes" do
193
+ map = Rack::URLMap.new("/" => lambda { |env|
194
+ [200,
195
+ { "Content-Type" => "text/plain",
196
+ "X-Position" => "root",
197
+ "X-PathInfo" => env["PATH_INFO"],
198
+ "X-ScriptName" => env["SCRIPT_NAME"]
199
+ }, [""]]},
200
+ "/foo" => lambda { |env|
201
+ [200,
202
+ { "Content-Type" => "text/plain",
203
+ "X-Position" => "foo",
204
+ "X-PathInfo" => env["PATH_INFO"],
205
+ "X-ScriptName" => env["SCRIPT_NAME"]
206
+ }, [""]]}
207
+ )
208
+
209
+ res = Rack::MockRequest.new(map).get("/http://example.org/bar")
210
+ res.should.be.ok
211
+ res["X-Position"].should.equal "root"
212
+ res["X-PathInfo"].should.equal "/http://example.org/bar"
213
+ res["X-ScriptName"].should.equal ""
214
+ end
215
+ end
@@ -0,0 +1,554 @@
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 escape correctly for multibyte characters" do
16
+ matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
17
+ matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
18
+ Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
19
+ matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
20
+ matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
21
+ Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
22
+ end
23
+
24
+ specify "should unescape correctly" do
25
+ Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
26
+ Rack::Utils.unescape("a+space").should.equal "a space"
27
+ Rack::Utils.unescape("a%20space").should.equal "a space"
28
+ Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
29
+ should.equal "q1!2\"'w$5&7/z8)?\\"
30
+ end
31
+
32
+ specify "should parse query strings correctly" do
33
+ Rack::Utils.parse_query("foo=bar").
34
+ should.equal "foo" => "bar"
35
+ Rack::Utils.parse_query("foo=\"bar\"").
36
+ should.equal "foo" => "\"bar\""
37
+ Rack::Utils.parse_query("foo=bar&foo=quux").
38
+ should.equal "foo" => ["bar", "quux"]
39
+ Rack::Utils.parse_query("foo=1&bar=2").
40
+ should.equal "foo" => "1", "bar" => "2"
41
+ Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
42
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
43
+ Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar"
44
+ end
45
+
46
+ specify "should parse nested query strings correctly" do
47
+ Rack::Utils.parse_nested_query("foo").
48
+ should.equal "foo" => nil
49
+ Rack::Utils.parse_nested_query("foo=").
50
+ should.equal "foo" => ""
51
+ Rack::Utils.parse_nested_query("foo=bar").
52
+ should.equal "foo" => "bar"
53
+ Rack::Utils.parse_nested_query("foo=\"bar\"").
54
+ should.equal "foo" => "\"bar\""
55
+ Rack::Utils.parse_nested_query("foo=\"bar \n\"lalala\" baz\"").
56
+ should.equal "foo" => "\"bar \n\"lalala\" baz\""
57
+
58
+ Rack::Utils.parse_nested_query("foo=bar&foo=quux").
59
+ should.equal "foo" => "quux"
60
+ Rack::Utils.parse_nested_query("foo&foo=").
61
+ should.equal "foo" => ""
62
+ Rack::Utils.parse_nested_query("foo=1&bar=2").
63
+ should.equal "foo" => "1", "bar" => "2"
64
+ Rack::Utils.parse_nested_query("&foo=1&&bar=2").
65
+ should.equal "foo" => "1", "bar" => "2"
66
+ Rack::Utils.parse_nested_query("foo&bar=").
67
+ should.equal "foo" => nil, "bar" => ""
68
+ Rack::Utils.parse_nested_query("foo=bar&baz=").
69
+ should.equal "foo" => "bar", "baz" => ""
70
+ Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
71
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
72
+
73
+ Rack::Utils.parse_nested_query("foo[]").
74
+ should.equal "foo" => [nil]
75
+ Rack::Utils.parse_nested_query("foo[]=").
76
+ should.equal "foo" => [""]
77
+ Rack::Utils.parse_nested_query("foo[]=bar").
78
+ should.equal "foo" => ["bar"]
79
+
80
+ Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
81
+ should.equal "foo" => ["1", "2"]
82
+ Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
83
+ should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
84
+ Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
85
+ should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
86
+
87
+ Rack::Utils.parse_nested_query("x[y][z]=1").
88
+ should.equal "x" => {"y" => {"z" => "1"}}
89
+ Rack::Utils.parse_nested_query("x[y][z][]=1").
90
+ should.equal "x" => {"y" => {"z" => ["1"]}}
91
+ Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
92
+ should.equal "x" => {"y" => {"z" => "2"}}
93
+ Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
94
+ should.equal "x" => {"y" => {"z" => ["1", "2"]}}
95
+
96
+ Rack::Utils.parse_nested_query("x[y][][z]=1").
97
+ should.equal "x" => {"y" => [{"z" => "1"}]}
98
+ Rack::Utils.parse_nested_query("x[y][][z][]=1").
99
+ should.equal "x" => {"y" => [{"z" => ["1"]}]}
100
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
101
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
102
+
103
+ Rack::Utils.parse_nested_query("x[y][][v][w]=1").
104
+ should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
105
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
106
+ should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
107
+
108
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
109
+ should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
110
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
111
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
112
+
113
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
114
+ should.raise(TypeError).
115
+ message.should.equal "expected Hash (got String) for param `y'"
116
+
117
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
118
+ should.raise(TypeError).
119
+ message.should.equal "expected Array (got Hash) for param `x'"
120
+
121
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
122
+ should.raise(TypeError).
123
+ message.should.equal "expected Array (got String) for param `y'"
124
+ end
125
+
126
+ specify "should build query strings correctly" do
127
+ Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
128
+ Rack::Utils.build_query("foo" => ["bar", "quux"]).
129
+ should.equal "foo=bar&foo=quux"
130
+ Rack::Utils.build_query("foo" => "1", "bar" => "2").
131
+ should.equal "foo=1&bar=2"
132
+ Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?").
133
+ should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
134
+ end
135
+
136
+ specify "should build nested query strings correctly" do
137
+ Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
138
+ Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
139
+ Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
140
+
141
+ Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
142
+ should.equal "foo=1&bar=2"
143
+ Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
144
+ should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
145
+
146
+ Rack::Utils.build_nested_query("foo" => [nil]).
147
+ should.equal "foo[]"
148
+ Rack::Utils.build_nested_query("foo" => [""]).
149
+ should.equal "foo[]="
150
+ Rack::Utils.build_nested_query("foo" => ["bar"]).
151
+ should.equal "foo[]=bar"
152
+
153
+ # The ordering of the output query string is unpredictable with 1.8's
154
+ # unordered hash. Test that build_nested_query performs the inverse
155
+ # function of parse_nested_query.
156
+ [{"foo" => nil, "bar" => ""},
157
+ {"foo" => "bar", "baz" => ""},
158
+ {"foo" => ["1", "2"]},
159
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
160
+ {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
161
+ {"foo" => ["1", "2"]},
162
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
163
+ {"x" => {"y" => {"z" => "1"}}},
164
+ {"x" => {"y" => {"z" => ["1"]}}},
165
+ {"x" => {"y" => {"z" => ["1", "2"]}}},
166
+ {"x" => {"y" => [{"z" => "1"}]}},
167
+ {"x" => {"y" => [{"z" => ["1"]}]}},
168
+ {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
169
+ {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
170
+ {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
171
+ {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
172
+ {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
173
+ ].each { |params|
174
+ qs = Rack::Utils.build_nested_query(params)
175
+ Rack::Utils.parse_nested_query(qs).should.equal params
176
+ }
177
+
178
+ lambda { Rack::Utils.build_nested_query("foo=bar") }.
179
+ should.raise(ArgumentError).
180
+ message.should.equal "value must be a Hash"
181
+ end
182
+
183
+ specify "should figure out which encodings are acceptable" do
184
+ helper = lambda do |a, b|
185
+ request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
186
+ Rack::Utils.select_best_encoding(a, b)
187
+ end
188
+
189
+ helper.call(%w(), [["x", 1]]).should.equal(nil)
190
+ helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil)
191
+ helper.call(%w(identity), [["*", 0.0]]).should.equal(nil)
192
+
193
+ helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity")
194
+
195
+ helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress")
196
+ helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip")
197
+
198
+ helper.call(%w(foo bar identity), []).should.equal("identity")
199
+ helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo")
200
+ helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar")
201
+
202
+ helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
203
+ helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
204
+ end
205
+
206
+ specify "should return the bytesize of String" do
207
+ Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
208
+ end
209
+
210
+ specify "should return status code for integer" do
211
+ Rack::Utils.status_code(200).should.equal 200
212
+ end
213
+
214
+ specify "should return status code for string" do
215
+ Rack::Utils.status_code("200").should.equal 200
216
+ end
217
+
218
+ specify "should return status code for symbol" do
219
+ Rack::Utils.status_code(:ok).should.equal 200
220
+ end
221
+ end
222
+
223
+ context "Rack::Utils::HeaderHash" do
224
+ specify "should retain header case" do
225
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
226
+ h['ETag'] = 'Boo!'
227
+ h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
228
+ end
229
+
230
+ specify "should check existence of keys case insensitively" do
231
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
232
+ h.should.include 'content-md5'
233
+ h.should.not.include 'ETag'
234
+ end
235
+
236
+ specify "should merge case-insensitively" do
237
+ h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
238
+ merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
239
+ merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
240
+ end
241
+
242
+ specify "should overwrite case insensitively and assume the new key's case" do
243
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
244
+ h["foo-bar"] = "bizzle"
245
+ h["FOO-BAR"].should.equal "bizzle"
246
+ h.length.should.equal 1
247
+ h.to_hash.should.equal "foo-bar" => "bizzle"
248
+ end
249
+
250
+ specify "should be converted to real Hash" do
251
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
252
+ h.to_hash.should.be.instance_of Hash
253
+ end
254
+
255
+ specify "should convert Array values to Strings when converting to Hash" do
256
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
257
+ h.to_hash.should.equal({ "foo" => "bar\nbaz" })
258
+ end
259
+
260
+ specify "should replace hashes correctly" do
261
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
262
+ j = {"foo" => "bar"}
263
+ h.replace(j)
264
+ h["foo"].should.equal "bar"
265
+ end
266
+
267
+ specify "should be able to delete the given key case-sensitively" do
268
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
269
+ h.delete("foo")
270
+ h["foo"].should.be.nil
271
+ h["FOO"].should.be.nil
272
+ end
273
+
274
+ specify "should be able to delete the given key case-insensitively" do
275
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
276
+ h.delete("FOO")
277
+ h["foo"].should.be.nil
278
+ h["FOO"].should.be.nil
279
+ end
280
+
281
+ specify "should return the deleted value when #delete is called on an existing key" do
282
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
283
+ h.delete("Foo").should.equal("bar")
284
+ end
285
+
286
+ specify "should return nil when #delete is called on a non-existant key" do
287
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
288
+ h.delete("Hello").should.be.nil
289
+ end
290
+
291
+ specify "should avoid unnecessary object creation if possible" do
292
+ a = Rack::Utils::HeaderHash.new("foo" => "bar")
293
+ b = Rack::Utils::HeaderHash.new(a)
294
+ b.object_id.should.equal(a.object_id)
295
+ b.should.equal(a)
296
+ end
297
+
298
+ specify "should convert Array values to Strings when responding to #each" do
299
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
300
+ h.each do |k,v|
301
+ k.should.equal("foo")
302
+ v.should.equal("bar\nbaz")
303
+ end
304
+ end
305
+
306
+ end
307
+
308
+ context "Rack::Utils::Context" do
309
+ class ContextTest
310
+ attr_reader :app
311
+ def initialize app; @app=app; end
312
+ def call env; context env; end
313
+ def context env, app=@app; app.call(env); end
314
+ end
315
+ test_target1 = proc{|e| e.to_s+' world' }
316
+ test_target2 = proc{|e| e.to_i+2 }
317
+ test_target3 = proc{|e| nil }
318
+ test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
319
+ test_app = ContextTest.new test_target4
320
+
321
+ specify "should set context correctly" do
322
+ test_app.app.should.equal test_target4
323
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
324
+ c1.for.should.equal test_app
325
+ c1.app.should.equal test_target1
326
+ c2 = Rack::Utils::Context.new(test_app, test_target2)
327
+ c2.for.should.equal test_app
328
+ c2.app.should.equal test_target2
329
+ end
330
+
331
+ specify "should alter app on recontexting" do
332
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
333
+ c2 = c1.recontext(test_target2)
334
+ c2.for.should.equal test_app
335
+ c2.app.should.equal test_target2
336
+ c3 = c2.recontext(test_target3)
337
+ c3.for.should.equal test_app
338
+ c3.app.should.equal test_target3
339
+ end
340
+
341
+ specify "should run different apps" do
342
+ c1 = Rack::Utils::Context.new test_app, test_target1
343
+ c2 = c1.recontext test_target2
344
+ c3 = c2.recontext test_target3
345
+ c4 = c3.recontext test_target4
346
+ a4 = Rack::Lint.new c4
347
+ a5 = Rack::Lint.new test_app
348
+ r1 = c1.call('hello')
349
+ r1.should.equal 'hello world'
350
+ r2 = c2.call(2)
351
+ r2.should.equal 4
352
+ r3 = c3.call(:misc_symbol)
353
+ r3.should.be.nil
354
+ r4 = Rack::MockRequest.new(a4).get('/')
355
+ r4.status.should.be 200
356
+ r5 = Rack::MockRequest.new(a5).get('/')
357
+ r5.status.should.be 200
358
+ r4.body.should.equal r5.body
359
+ end
360
+ end
361
+
362
+ context "Rack::Utils::Multipart" do
363
+ specify "should return nil if content type is not multipart" do
364
+ env = Rack::MockRequest.env_for("/",
365
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded')
366
+ Rack::Utils::Multipart.parse_multipart(env).should.equal nil
367
+ end
368
+
369
+ specify "should parse multipart upload with text file" do
370
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
371
+ params = Rack::Utils::Multipart.parse_multipart(env)
372
+ params["submit-name"].should.equal "Larry"
373
+ params["files"][:type].should.equal "text/plain"
374
+ params["files"][:filename].should.equal "file1.txt"
375
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
376
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
377
+ "Content-Type: text/plain\r\n"
378
+ params["files"][:name].should.equal "files"
379
+ params["files"][:tempfile].read.should.equal "contents"
380
+ end
381
+
382
+ specify "should parse multipart upload with nested parameters" do
383
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
384
+ params = Rack::Utils::Multipart.parse_multipart(env)
385
+ params["foo"]["submit-name"].should.equal "Larry"
386
+ params["foo"]["files"][:type].should.equal "text/plain"
387
+ params["foo"]["files"][:filename].should.equal "file1.txt"
388
+ params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
389
+ "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
390
+ "Content-Type: text/plain\r\n"
391
+ params["foo"]["files"][:name].should.equal "foo[files]"
392
+ params["foo"]["files"][:tempfile].read.should.equal "contents"
393
+ end
394
+
395
+ specify "should parse multipart upload with binary file" do
396
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
397
+ params = Rack::Utils::Multipart.parse_multipart(env)
398
+ params["submit-name"].should.equal "Larry"
399
+ params["files"][:type].should.equal "image/png"
400
+ params["files"][:filename].should.equal "rack-logo.png"
401
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
402
+ "name=\"files\"; filename=\"rack-logo.png\"\r\n" +
403
+ "Content-Type: image/png\r\n"
404
+ params["files"][:name].should.equal "files"
405
+ params["files"][:tempfile].read.length.should.equal 26473
406
+ end
407
+
408
+ specify "should parse multipart upload with empty file" do
409
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
410
+ params = Rack::Utils::Multipart.parse_multipart(env)
411
+ params["submit-name"].should.equal "Larry"
412
+ params["files"][:type].should.equal "text/plain"
413
+ params["files"][:filename].should.equal "file1.txt"
414
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
415
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
416
+ "Content-Type: text/plain\r\n"
417
+ params["files"][:name].should.equal "files"
418
+ params["files"][:tempfile].read.should.equal ""
419
+ end
420
+
421
+ specify "should parse multipart upload with filename with semicolons" do
422
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon))
423
+ params = Rack::Utils::Multipart.parse_multipart(env)
424
+ params["files"][:type].should.equal "text/plain"
425
+ params["files"][:filename].should.equal "fi;le1.txt"
426
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
427
+ "name=\"files\"; filename=\"fi;le1.txt\"\r\n" +
428
+ "Content-Type: text/plain\r\n"
429
+ params["files"][:name].should.equal "files"
430
+ params["files"][:tempfile].read.should.equal "contents"
431
+ end
432
+
433
+ specify "should not include file params if no file was selected" do
434
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
435
+ params = Rack::Utils::Multipart.parse_multipart(env)
436
+ params["submit-name"].should.equal "Larry"
437
+ params["files"].should.equal nil
438
+ params.keys.should.not.include "files"
439
+ end
440
+
441
+ specify "should parse IE multipart upload and clean up filename" do
442
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
443
+ params = Rack::Utils::Multipart.parse_multipart(env)
444
+ params["files"][:type].should.equal "text/plain"
445
+ params["files"][:filename].should.equal "file1.txt"
446
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
447
+ "name=\"files\"; " +
448
+ 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
449
+ "\r\nContent-Type: text/plain\r\n"
450
+ params["files"][:name].should.equal "files"
451
+ params["files"][:tempfile].read.should.equal "contents"
452
+ end
453
+
454
+ specify "rewinds input after parsing upload" do
455
+ options = multipart_fixture(:text)
456
+ input = options[:input]
457
+ env = Rack::MockRequest.env_for("/", options)
458
+ params = Rack::Utils::Multipart.parse_multipart(env)
459
+ params["submit-name"].should.equal "Larry"
460
+ params["files"][:filename].should.equal "file1.txt"
461
+ input.read.length.should.equal 197
462
+ end
463
+
464
+ specify "builds multipart body" do
465
+ files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
466
+ data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
467
+
468
+ options = {
469
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
470
+ "CONTENT_LENGTH" => data.length.to_s,
471
+ :input => StringIO.new(data)
472
+ }
473
+ env = Rack::MockRequest.env_for("/", options)
474
+ params = Rack::Utils::Multipart.parse_multipart(env)
475
+ params["submit-name"].should.equal "Larry"
476
+ params["files"][:filename].should.equal "file1.txt"
477
+ params["files"][:tempfile].read.should.equal "contents"
478
+ end
479
+
480
+ specify "builds nested multipart body" do
481
+ files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
482
+ data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
483
+
484
+ options = {
485
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
486
+ "CONTENT_LENGTH" => data.length.to_s,
487
+ :input => StringIO.new(data)
488
+ }
489
+ env = Rack::MockRequest.env_for("/", options)
490
+ params = Rack::Utils::Multipart.parse_multipart(env)
491
+ params["people"][0]["submit-name"].should.equal "Larry"
492
+ params["people"][0]["files"][:filename].should.equal "file1.txt"
493
+ params["people"][0]["files"][:tempfile].read.should.equal "contents"
494
+ end
495
+
496
+ specify "can parse fields that end at the end of the buffer" do
497
+ input = File.read(multipart_file("bad_robots"))
498
+
499
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
500
+ "CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
501
+ "CONTENT_LENGTH" => input.size,
502
+ :input => input)
503
+
504
+ req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
505
+ req.POST['addresses'].should.not.equal nil
506
+ end
507
+
508
+ specify "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
509
+ data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
510
+ options = {
511
+ "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
512
+ "CONTENT_LENGTH" => data.length.to_s,
513
+ :input => StringIO.new(data)
514
+ }
515
+ env = Rack::MockRequest.env_for("/", options)
516
+ params = Rack::Utils::Multipart.parse_multipart(env)
517
+
518
+ params.should.not.equal nil
519
+ params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
520
+ params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
521
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
522
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
523
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
524
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
525
+ end
526
+
527
+ specify "should return nil if no UploadedFiles were used" do
528
+ data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
529
+ data.should.equal nil
530
+ end
531
+
532
+ specify "should raise ArgumentError if params is not a Hash" do
533
+ lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }.
534
+ should.raise(ArgumentError).
535
+ message.should.equal "value must be a Hash"
536
+ end
537
+
538
+ private
539
+ def multipart_fixture(name)
540
+ file = multipart_file(name)
541
+ data = File.open(file, 'rb') { |io| io.read }
542
+
543
+ type = "multipart/form-data; boundary=AaB03x"
544
+ length = data.respond_to?(:bytesize) ? data.bytesize : data.size
545
+
546
+ { "CONTENT_TYPE" => type,
547
+ "CONTENT_LENGTH" => length.to_s,
548
+ :input => StringIO.new(data) }
549
+ end
550
+
551
+ def multipart_file(name)
552
+ File.join(File.dirname(__FILE__), "multipart", name.to_s)
553
+ end
554
+ end