eac-rack 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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