edgar-rack 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +401 -0
  4. data/Rakefile +101 -0
  5. data/SPEC +171 -0
  6. data/bin/rackup +4 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +81 -0
  12. data/lib/rack/auth/abstract/handler.rb +37 -0
  13. data/lib/rack/auth/abstract/request.rb +43 -0
  14. data/lib/rack/auth/basic.rb +58 -0
  15. data/lib/rack/auth/digest/md5.rb +124 -0
  16. data/lib/rack/auth/digest/nonce.rb +51 -0
  17. data/lib/rack/auth/digest/params.rb +53 -0
  18. data/lib/rack/auth/digest/request.rb +40 -0
  19. data/lib/rack/builder.rb +80 -0
  20. data/lib/rack/cascade.rb +41 -0
  21. data/lib/rack/chunked.rb +52 -0
  22. data/lib/rack/commonlogger.rb +49 -0
  23. data/lib/rack/conditionalget.rb +63 -0
  24. data/lib/rack/config.rb +15 -0
  25. data/lib/rack/content_length.rb +29 -0
  26. data/lib/rack/content_type.rb +23 -0
  27. data/lib/rack/deflater.rb +96 -0
  28. data/lib/rack/directory.rb +157 -0
  29. data/lib/rack/etag.rb +59 -0
  30. data/lib/rack/file.rb +118 -0
  31. data/lib/rack/handler.rb +88 -0
  32. data/lib/rack/handler/cgi.rb +61 -0
  33. data/lib/rack/handler/evented_mongrel.rb +8 -0
  34. data/lib/rack/handler/fastcgi.rb +90 -0
  35. data/lib/rack/handler/lsws.rb +61 -0
  36. data/lib/rack/handler/mongrel.rb +90 -0
  37. data/lib/rack/handler/scgi.rb +59 -0
  38. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  39. data/lib/rack/handler/thin.rb +17 -0
  40. data/lib/rack/handler/webrick.rb +73 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +567 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +44 -0
  45. data/lib/rack/logger.rb +18 -0
  46. data/lib/rack/methodoverride.rb +27 -0
  47. data/lib/rack/mime.rb +210 -0
  48. data/lib/rack/mock.rb +185 -0
  49. data/lib/rack/nulllogger.rb +18 -0
  50. data/lib/rack/recursive.rb +61 -0
  51. data/lib/rack/reloader.rb +109 -0
  52. data/lib/rack/request.rb +307 -0
  53. data/lib/rack/response.rb +151 -0
  54. data/lib/rack/rewindable_input.rb +104 -0
  55. data/lib/rack/runtime.rb +27 -0
  56. data/lib/rack/sendfile.rb +139 -0
  57. data/lib/rack/server.rb +289 -0
  58. data/lib/rack/session/abstract/id.rb +348 -0
  59. data/lib/rack/session/cookie.rb +152 -0
  60. data/lib/rack/session/memcache.rb +93 -0
  61. data/lib/rack/session/pool.rb +79 -0
  62. data/lib/rack/showexceptions.rb +378 -0
  63. data/lib/rack/showstatus.rb +113 -0
  64. data/lib/rack/static.rb +53 -0
  65. data/lib/rack/urlmap.rb +55 -0
  66. data/lib/rack/utils.rb +698 -0
  67. data/rack.gemspec +39 -0
  68. data/test/cgi/lighttpd.conf +25 -0
  69. data/test/cgi/rackup_stub.rb +6 -0
  70. data/test/cgi/sample_rackup.ru +5 -0
  71. data/test/cgi/test +9 -0
  72. data/test/cgi/test.fcgi +8 -0
  73. data/test/cgi/test.ru +5 -0
  74. data/test/gemloader.rb +6 -0
  75. data/test/multipart/bad_robots +259 -0
  76. data/test/multipart/binary +0 -0
  77. data/test/multipart/empty +10 -0
  78. data/test/multipart/fail_16384_nofile +814 -0
  79. data/test/multipart/file1.txt +1 -0
  80. data/test/multipart/filename_and_modification_param +7 -0
  81. data/test/multipart/filename_with_escaped_quotes +6 -0
  82. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  83. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  84. data/test/multipart/filename_with_unescaped_quotes +6 -0
  85. data/test/multipart/ie +6 -0
  86. data/test/multipart/nested +10 -0
  87. data/test/multipart/none +9 -0
  88. data/test/multipart/semicolon +6 -0
  89. data/test/multipart/text +15 -0
  90. data/test/rackup/config.ru +31 -0
  91. data/test/spec_auth_basic.rb +70 -0
  92. data/test/spec_auth_digest.rb +241 -0
  93. data/test/spec_builder.rb +123 -0
  94. data/test/spec_cascade.rb +45 -0
  95. data/test/spec_cgi.rb +102 -0
  96. data/test/spec_chunked.rb +60 -0
  97. data/test/spec_commonlogger.rb +56 -0
  98. data/test/spec_conditionalget.rb +86 -0
  99. data/test/spec_config.rb +23 -0
  100. data/test/spec_content_length.rb +36 -0
  101. data/test/spec_content_type.rb +29 -0
  102. data/test/spec_deflater.rb +125 -0
  103. data/test/spec_directory.rb +57 -0
  104. data/test/spec_etag.rb +75 -0
  105. data/test/spec_fastcgi.rb +107 -0
  106. data/test/spec_file.rb +92 -0
  107. data/test/spec_handler.rb +49 -0
  108. data/test/spec_head.rb +30 -0
  109. data/test/spec_lint.rb +515 -0
  110. data/test/spec_lobster.rb +43 -0
  111. data/test/spec_lock.rb +142 -0
  112. data/test/spec_logger.rb +28 -0
  113. data/test/spec_methodoverride.rb +58 -0
  114. data/test/spec_mock.rb +241 -0
  115. data/test/spec_mongrel.rb +182 -0
  116. data/test/spec_nulllogger.rb +12 -0
  117. data/test/spec_recursive.rb +69 -0
  118. data/test/spec_request.rb +774 -0
  119. data/test/spec_response.rb +245 -0
  120. data/test/spec_rewindable_input.rb +118 -0
  121. data/test/spec_runtime.rb +39 -0
  122. data/test/spec_sendfile.rb +83 -0
  123. data/test/spec_server.rb +8 -0
  124. data/test/spec_session_abstract_id.rb +43 -0
  125. data/test/spec_session_cookie.rb +171 -0
  126. data/test/spec_session_memcache.rb +289 -0
  127. data/test/spec_session_pool.rb +200 -0
  128. data/test/spec_showexceptions.rb +87 -0
  129. data/test/spec_showstatus.rb +79 -0
  130. data/test/spec_static.rb +48 -0
  131. data/test/spec_thin.rb +86 -0
  132. data/test/spec_urlmap.rb +213 -0
  133. data/test/spec_utils.rb +678 -0
  134. data/test/spec_webrick.rb +141 -0
  135. data/test/testrequest.rb +78 -0
  136. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  137. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  138. metadata +329 -0
@@ -0,0 +1,213 @@
1
+ require 'rack/urlmap'
2
+ require 'rack/mock'
3
+
4
+ describe Rack::URLMap do
5
+ it "dispatches paths correctly" do
6
+ app = lambda { |env|
7
+ [200, {
8
+ 'X-ScriptName' => env['SCRIPT_NAME'],
9
+ 'X-PathInfo' => env['PATH_INFO'],
10
+ 'Content-Type' => 'text/plain'
11
+ }, [""]]
12
+ }
13
+ map = Rack::URLMap.new({
14
+ 'http://foo.org/bar' => app,
15
+ '/foo' => app,
16
+ '/foo/bar' => app
17
+ })
18
+
19
+ res = Rack::MockRequest.new(map).get("/")
20
+ res.should.be.not_found
21
+
22
+ res = Rack::MockRequest.new(map).get("/qux")
23
+ res.should.be.not_found
24
+
25
+ res = Rack::MockRequest.new(map).get("/foo")
26
+ res.should.be.ok
27
+ res["X-ScriptName"].should.equal "/foo"
28
+ res["X-PathInfo"].should.equal ""
29
+
30
+ res = Rack::MockRequest.new(map).get("/foo/")
31
+ res.should.be.ok
32
+ res["X-ScriptName"].should.equal "/foo"
33
+ res["X-PathInfo"].should.equal "/"
34
+
35
+ res = Rack::MockRequest.new(map).get("/foo/bar")
36
+ res.should.be.ok
37
+ res["X-ScriptName"].should.equal "/foo/bar"
38
+ res["X-PathInfo"].should.equal ""
39
+
40
+ res = Rack::MockRequest.new(map).get("/foo/bar/")
41
+ res.should.be.ok
42
+ res["X-ScriptName"].should.equal "/foo/bar"
43
+ res["X-PathInfo"].should.equal "/"
44
+
45
+ res = Rack::MockRequest.new(map).get("/foo///bar//quux")
46
+ res.status.should.equal 200
47
+ res.should.be.ok
48
+ res["X-ScriptName"].should.equal "/foo/bar"
49
+ res["X-PathInfo"].should.equal "//quux"
50
+
51
+ res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh")
52
+ res.should.be.ok
53
+ res["X-ScriptName"].should.equal "/bleh/foo"
54
+ res["X-PathInfo"].should.equal "/quux"
55
+
56
+ res = Rack::MockRequest.new(map).get("/bar", 'HTTP_HOST' => 'foo.org')
57
+ res.should.be.ok
58
+ res["X-ScriptName"].should.equal "/bar"
59
+ res["X-PathInfo"].should.be.empty
60
+
61
+ res = Rack::MockRequest.new(map).get("/bar/", 'HTTP_HOST' => 'foo.org')
62
+ res.should.be.ok
63
+ res["X-ScriptName"].should.equal "/bar"
64
+ res["X-PathInfo"].should.equal '/'
65
+ end
66
+
67
+
68
+ it "dispatches hosts correctly" do
69
+ map = Rack::URLMap.new("http://foo.org/" => lambda { |env|
70
+ [200,
71
+ { "Content-Type" => "text/plain",
72
+ "X-Position" => "foo.org",
73
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
74
+ }, [""]]},
75
+ "http://subdomain.foo.org/" => lambda { |env|
76
+ [200,
77
+ { "Content-Type" => "text/plain",
78
+ "X-Position" => "subdomain.foo.org",
79
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
80
+ }, [""]]},
81
+ "http://bar.org/" => lambda { |env|
82
+ [200,
83
+ { "Content-Type" => "text/plain",
84
+ "X-Position" => "bar.org",
85
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
86
+ }, [""]]},
87
+ "/" => lambda { |env|
88
+ [200,
89
+ { "Content-Type" => "text/plain",
90
+ "X-Position" => "default.org",
91
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
92
+ }, [""]]}
93
+ )
94
+
95
+ res = Rack::MockRequest.new(map).get("/")
96
+ res.should.be.ok
97
+ res["X-Position"].should.equal "default.org"
98
+
99
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "bar.org")
100
+ res.should.be.ok
101
+ res["X-Position"].should.equal "bar.org"
102
+
103
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "foo.org")
104
+ res.should.be.ok
105
+ res["X-Position"].should.equal "foo.org"
106
+
107
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "subdomain.foo.org", "SERVER_NAME" => "foo.org")
108
+ res.should.be.ok
109
+ res["X-Position"].should.equal "subdomain.foo.org"
110
+
111
+ res = Rack::MockRequest.new(map).get("http://foo.org/")
112
+ res.should.be.ok
113
+ res["X-Position"].should.equal "default.org"
114
+
115
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org")
116
+ res.should.be.ok
117
+ res["X-Position"].should.equal "default.org"
118
+
119
+ res = Rack::MockRequest.new(map).get("/",
120
+ "HTTP_HOST" => "example.org:9292",
121
+ "SERVER_PORT" => "9292")
122
+ res.should.be.ok
123
+ res["X-Position"].should.equal "default.org"
124
+ end
125
+
126
+ should "be nestable" do
127
+ map = Rack::URLMap.new("/foo" =>
128
+ Rack::URLMap.new("/bar" =>
129
+ Rack::URLMap.new("/quux" => lambda { |env|
130
+ [200,
131
+ { "Content-Type" => "text/plain",
132
+ "X-Position" => "/foo/bar/quux",
133
+ "X-PathInfo" => env["PATH_INFO"],
134
+ "X-ScriptName" => env["SCRIPT_NAME"],
135
+ }, [""]]}
136
+ )))
137
+
138
+ res = Rack::MockRequest.new(map).get("/foo/bar")
139
+ res.should.be.not_found
140
+
141
+ res = Rack::MockRequest.new(map).get("/foo/bar/quux")
142
+ res.should.be.ok
143
+ res["X-Position"].should.equal "/foo/bar/quux"
144
+ res["X-PathInfo"].should.equal ""
145
+ res["X-ScriptName"].should.equal "/foo/bar/quux"
146
+ end
147
+
148
+ should "route root apps correctly" do
149
+ map = Rack::URLMap.new("/" => lambda { |env|
150
+ [200,
151
+ { "Content-Type" => "text/plain",
152
+ "X-Position" => "root",
153
+ "X-PathInfo" => env["PATH_INFO"],
154
+ "X-ScriptName" => env["SCRIPT_NAME"]
155
+ }, [""]]},
156
+ "/foo" => lambda { |env|
157
+ [200,
158
+ { "Content-Type" => "text/plain",
159
+ "X-Position" => "foo",
160
+ "X-PathInfo" => env["PATH_INFO"],
161
+ "X-ScriptName" => env["SCRIPT_NAME"]
162
+ }, [""]]}
163
+ )
164
+
165
+ res = Rack::MockRequest.new(map).get("/foo/bar")
166
+ res.should.be.ok
167
+ res["X-Position"].should.equal "foo"
168
+ res["X-PathInfo"].should.equal "/bar"
169
+ res["X-ScriptName"].should.equal "/foo"
170
+
171
+ res = Rack::MockRequest.new(map).get("/foo")
172
+ res.should.be.ok
173
+ res["X-Position"].should.equal "foo"
174
+ res["X-PathInfo"].should.equal ""
175
+ res["X-ScriptName"].should.equal "/foo"
176
+
177
+ res = Rack::MockRequest.new(map).get("/bar")
178
+ res.should.be.ok
179
+ res["X-Position"].should.equal "root"
180
+ res["X-PathInfo"].should.equal "/bar"
181
+ res["X-ScriptName"].should.equal ""
182
+
183
+ res = Rack::MockRequest.new(map).get("")
184
+ res.should.be.ok
185
+ res["X-Position"].should.equal "root"
186
+ res["X-PathInfo"].should.equal "/"
187
+ res["X-ScriptName"].should.equal ""
188
+ end
189
+
190
+ should "not squeeze slashes" do
191
+ map = Rack::URLMap.new("/" => lambda { |env|
192
+ [200,
193
+ { "Content-Type" => "text/plain",
194
+ "X-Position" => "root",
195
+ "X-PathInfo" => env["PATH_INFO"],
196
+ "X-ScriptName" => env["SCRIPT_NAME"]
197
+ }, [""]]},
198
+ "/foo" => lambda { |env|
199
+ [200,
200
+ { "Content-Type" => "text/plain",
201
+ "X-Position" => "foo",
202
+ "X-PathInfo" => env["PATH_INFO"],
203
+ "X-ScriptName" => env["SCRIPT_NAME"]
204
+ }, [""]]}
205
+ )
206
+
207
+ res = Rack::MockRequest.new(map).get("/http://example.org/bar")
208
+ res.should.be.ok
209
+ res["X-Position"].should.equal "root"
210
+ res["X-PathInfo"].should.equal "/http://example.org/bar"
211
+ res["X-ScriptName"].should.equal ""
212
+ end
213
+ end
@@ -0,0 +1,678 @@
1
+ require 'rack/utils'
2
+ require 'rack/mock'
3
+
4
+ describe Rack::Utils do
5
+ should "escape correctly" do
6
+ Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
7
+ Rack::Utils.escape("a space").should.equal "a+space"
8
+ Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
9
+ should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
10
+ end
11
+
12
+ should "escape correctly for multibyte characters" do
13
+ matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
14
+ matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
15
+ Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
16
+ matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
17
+ matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
18
+ Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
19
+ end
20
+
21
+ should "unescape correctly" do
22
+ Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
23
+ Rack::Utils.unescape("a+space").should.equal "a space"
24
+ Rack::Utils.unescape("a%20space").should.equal "a space"
25
+ Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
26
+ should.equal "q1!2\"'w$5&7/z8)?\\"
27
+ end
28
+
29
+ should "parse query strings correctly" do
30
+ Rack::Utils.parse_query("foo=bar").
31
+ should.equal "foo" => "bar"
32
+ Rack::Utils.parse_query("foo=\"bar\"").
33
+ should.equal "foo" => "\"bar\""
34
+ Rack::Utils.parse_query("foo=bar&foo=quux").
35
+ should.equal "foo" => ["bar", "quux"]
36
+ Rack::Utils.parse_query("foo=1&bar=2").
37
+ should.equal "foo" => "1", "bar" => "2"
38
+ Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
39
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
40
+ Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar"
41
+ end
42
+
43
+ should "parse nested query strings correctly" do
44
+ Rack::Utils.parse_nested_query("foo").
45
+ should.equal "foo" => nil
46
+ Rack::Utils.parse_nested_query("foo=").
47
+ should.equal "foo" => ""
48
+ Rack::Utils.parse_nested_query("foo=bar").
49
+ should.equal "foo" => "bar"
50
+ Rack::Utils.parse_nested_query("foo=\"bar\"").
51
+ should.equal "foo" => "\"bar\""
52
+
53
+ Rack::Utils.parse_nested_query("foo=bar&foo=quux").
54
+ should.equal "foo" => "quux"
55
+ Rack::Utils.parse_nested_query("foo&foo=").
56
+ should.equal "foo" => ""
57
+ Rack::Utils.parse_nested_query("foo=1&bar=2").
58
+ should.equal "foo" => "1", "bar" => "2"
59
+ Rack::Utils.parse_nested_query("&foo=1&&bar=2").
60
+ should.equal "foo" => "1", "bar" => "2"
61
+ Rack::Utils.parse_nested_query("foo&bar=").
62
+ should.equal "foo" => nil, "bar" => ""
63
+ Rack::Utils.parse_nested_query("foo=bar&baz=").
64
+ should.equal "foo" => "bar", "baz" => ""
65
+ Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
66
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
67
+
68
+ Rack::Utils.parse_nested_query("foo[]").
69
+ should.equal "foo" => [nil]
70
+ Rack::Utils.parse_nested_query("foo[]=").
71
+ should.equal "foo" => [""]
72
+ Rack::Utils.parse_nested_query("foo[]=bar").
73
+ should.equal "foo" => ["bar"]
74
+
75
+ Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
76
+ should.equal "foo" => ["1", "2"]
77
+ Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
78
+ should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
79
+ Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
80
+ should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
81
+
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").
85
+ should.equal "x" => {"y" => {"z" => ["1"]}}
86
+ Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
87
+ should.equal "x" => {"y" => {"z" => "2"}}
88
+ Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
89
+ should.equal "x" => {"y" => {"z" => ["1", "2"]}}
90
+
91
+ Rack::Utils.parse_nested_query("x[y][][z]=1").
92
+ should.equal "x" => {"y" => [{"z" => "1"}]}
93
+ Rack::Utils.parse_nested_query("x[y][][z][]=1").
94
+ should.equal "x" => {"y" => [{"z" => ["1"]}]}
95
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
96
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
97
+
98
+ Rack::Utils.parse_nested_query("x[y][][v][w]=1").
99
+ should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
100
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
101
+ should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
102
+
103
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
104
+ should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
105
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
106
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
107
+
108
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
109
+ should.raise(TypeError).
110
+ message.should.equal "expected Hash (got String) for param `y'"
111
+
112
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
113
+ should.raise(TypeError).
114
+ message.should.equal "expected Array (got Hash) for param `x'"
115
+
116
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
117
+ should.raise(TypeError).
118
+ message.should.equal "expected Array (got String) for param `y'"
119
+ end
120
+
121
+ should "build query strings correctly" do
122
+ Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
123
+ Rack::Utils.build_query("foo" => ["bar", "quux"]).
124
+ should.equal "foo=bar&foo=quux"
125
+ Rack::Utils.build_query("foo" => "1", "bar" => "2").
126
+ should.equal "foo=1&bar=2"
127
+ Rack::Utils.build_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
+ end
130
+
131
+ should "build nested query strings correctly" do
132
+ Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
133
+ Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
134
+ Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
135
+
136
+ Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
137
+ should.equal "foo=1&bar=2"
138
+ Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
139
+ should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
140
+
141
+ Rack::Utils.build_nested_query("foo" => [nil]).
142
+ should.equal "foo[]"
143
+ Rack::Utils.build_nested_query("foo" => [""]).
144
+ should.equal "foo[]="
145
+ Rack::Utils.build_nested_query("foo" => ["bar"]).
146
+ should.equal "foo[]=bar"
147
+
148
+ # The ordering of the output query string is unpredictable with 1.8's
149
+ # unordered hash. Test that build_nested_query performs the inverse
150
+ # function of parse_nested_query.
151
+ [{"foo" => nil, "bar" => ""},
152
+ {"foo" => "bar", "baz" => ""},
153
+ {"foo" => ["1", "2"]},
154
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
155
+ {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
156
+ {"foo" => ["1", "2"]},
157
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
158
+ {"x" => {"y" => {"z" => "1"}}},
159
+ {"x" => {"y" => {"z" => ["1"]}}},
160
+ {"x" => {"y" => {"z" => ["1", "2"]}}},
161
+ {"x" => {"y" => [{"z" => "1"}]}},
162
+ {"x" => {"y" => [{"z" => ["1"]}]}},
163
+ {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
164
+ {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
165
+ {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
166
+ {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
167
+ {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
168
+ ].each { |params|
169
+ qs = Rack::Utils.build_nested_query(params)
170
+ Rack::Utils.parse_nested_query(qs).should.equal params
171
+ }
172
+
173
+ lambda { Rack::Utils.build_nested_query("foo=bar") }.
174
+ should.raise(ArgumentError).
175
+ message.should.equal "value must be a Hash"
176
+ end
177
+
178
+ should "should escape html entities [&><'\"/]" do
179
+ Rack::Utils.escape_html("foo").should.equal "foo"
180
+ Rack::Utils.escape_html("f&o").should.equal "f&amp;o"
181
+ Rack::Utils.escape_html("f<o").should.equal "f&lt;o"
182
+ Rack::Utils.escape_html("f>o").should.equal "f&gt;o"
183
+ Rack::Utils.escape_html("f'o").should.equal "f&#x27;o"
184
+ Rack::Utils.escape_html('f"o').should.equal "f&quot;o"
185
+ Rack::Utils.escape_html("f/o").should.equal "f&#x2F;o"
186
+ Rack::Utils.escape_html("<foo></foo>").should.equal "&lt;foo&gt;&lt;&#x2F;foo&gt;"
187
+ end
188
+
189
+ should "figure out which encodings are acceptable" do
190
+ helper = lambda do |a, b|
191
+ Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
192
+ Rack::Utils.select_best_encoding(a, b)
193
+ end
194
+
195
+ helper.call(%w(), [["x", 1]]).should.equal(nil)
196
+ helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil)
197
+ helper.call(%w(identity), [["*", 0.0]]).should.equal(nil)
198
+
199
+ helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity")
200
+
201
+ helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress")
202
+ helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip")
203
+
204
+ helper.call(%w(foo bar identity), []).should.equal("identity")
205
+ helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo")
206
+ helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar")
207
+
208
+ helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
209
+ helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
210
+ end
211
+
212
+ should "return the bytesize of String" do
213
+ Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
214
+ end
215
+
216
+ should "return status code for integer" do
217
+ Rack::Utils.status_code(200).should.equal 200
218
+ end
219
+
220
+ should "return status code for string" do
221
+ Rack::Utils.status_code("200").should.equal 200
222
+ end
223
+
224
+ should "return status code for symbol" do
225
+ Rack::Utils.status_code(:ok).should.equal 200
226
+ end
227
+ end
228
+
229
+ describe Rack::Utils, "byte_range" do
230
+ should "ignore missing or syntactically invalid byte ranges" do
231
+ Rack::Utils.byte_ranges({},500).should.equal nil
232
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).should.equal nil
233
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).should.equal nil
234
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).should.equal nil
235
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).should.equal nil
236
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).should.equal nil
237
+ # A range of non-positive length is syntactically invalid and ignored:
238
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).should.equal nil
239
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).should.equal nil
240
+ end
241
+
242
+ should "parse simple byte ranges" do
243
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).should.equal [(123..456)]
244
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).should.equal [(123..499)]
245
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).should.equal [(400..499)]
246
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).should.equal [(0..0)]
247
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).should.equal [(499..499)]
248
+ end
249
+
250
+ should "truncate byte ranges" do
251
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).should.equal [(123..499)]
252
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).should.equal []
253
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).should.equal [(0..499)]
254
+ end
255
+
256
+ should "ignore unsatisfiable byte ranges" do
257
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).should.equal []
258
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).should.equal []
259
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).should.equal []
260
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).should.equal []
261
+ end
262
+
263
+ should "handle byte ranges of empty files" do
264
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).should.equal []
265
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).should.equal []
266
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).should.equal []
267
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).should.equal []
268
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).should.equal []
269
+ end
270
+ end
271
+
272
+ describe Rack::Utils::HeaderHash do
273
+ should "retain header case" do
274
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
275
+ h['ETag'] = 'Boo!'
276
+ h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
277
+ end
278
+
279
+ should "check existence of keys case insensitively" do
280
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
281
+ h.should.include 'content-md5'
282
+ h.should.not.include 'ETag'
283
+ end
284
+
285
+ should "merge case-insensitively" do
286
+ h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
287
+ merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
288
+ merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
289
+ end
290
+
291
+ should "overwrite case insensitively and assume the new key's case" do
292
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
293
+ h["foo-bar"] = "bizzle"
294
+ h["FOO-BAR"].should.equal "bizzle"
295
+ h.length.should.equal 1
296
+ h.to_hash.should.equal "foo-bar" => "bizzle"
297
+ end
298
+
299
+ should "be converted to real Hash" do
300
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
301
+ h.to_hash.should.be.instance_of Hash
302
+ end
303
+
304
+ should "convert Array values to Strings when converting to Hash" do
305
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
306
+ h.to_hash.should.equal({ "foo" => "bar\nbaz" })
307
+ end
308
+
309
+ should "replace hashes correctly" do
310
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
311
+ j = {"foo" => "bar"}
312
+ h.replace(j)
313
+ h["foo"].should.equal "bar"
314
+ end
315
+
316
+ should "be able to delete the given key case-sensitively" do
317
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
318
+ h.delete("foo")
319
+ h["foo"].should.be.nil
320
+ h["FOO"].should.be.nil
321
+ end
322
+
323
+ should "be able to delete the given key case-insensitively" do
324
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
325
+ h.delete("FOO")
326
+ h["foo"].should.be.nil
327
+ h["FOO"].should.be.nil
328
+ end
329
+
330
+ should "return the deleted value when #delete is called on an existing key" do
331
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
332
+ h.delete("Foo").should.equal("bar")
333
+ end
334
+
335
+ should "return nil when #delete is called on a non-existant key" do
336
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
337
+ h.delete("Hello").should.be.nil
338
+ end
339
+
340
+ should "avoid unnecessary object creation if possible" do
341
+ a = Rack::Utils::HeaderHash.new("foo" => "bar")
342
+ b = Rack::Utils::HeaderHash.new(a)
343
+ b.object_id.should.equal(a.object_id)
344
+ b.should.equal(a)
345
+ end
346
+
347
+ should "convert Array values to Strings when responding to #each" do
348
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
349
+ h.each do |k,v|
350
+ k.should.equal("foo")
351
+ v.should.equal("bar\nbaz")
352
+ end
353
+ end
354
+
355
+ should "not create headers out of thin air" do
356
+ h = Rack::Utils::HeaderHash.new
357
+ h['foo']
358
+ h['foo'].should.be.nil
359
+ h.should.not.include 'foo'
360
+ end
361
+ end
362
+
363
+ describe Rack::Utils::Context do
364
+ class ContextTest
365
+ attr_reader :app
366
+ def initialize app; @app=app; end
367
+ def call env; context env; end
368
+ def context env, app=@app; app.call(env); end
369
+ end
370
+ test_target1 = proc{|e| e.to_s+' world' }
371
+ test_target2 = proc{|e| e.to_i+2 }
372
+ test_target3 = proc{|e| nil }
373
+ test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
374
+ test_app = ContextTest.new test_target4
375
+
376
+ should "set context correctly" do
377
+ test_app.app.should.equal test_target4
378
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
379
+ c1.for.should.equal test_app
380
+ c1.app.should.equal test_target1
381
+ c2 = Rack::Utils::Context.new(test_app, test_target2)
382
+ c2.for.should.equal test_app
383
+ c2.app.should.equal test_target2
384
+ end
385
+
386
+ should "alter app on recontexting" do
387
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
388
+ c2 = c1.recontext(test_target2)
389
+ c2.for.should.equal test_app
390
+ c2.app.should.equal test_target2
391
+ c3 = c2.recontext(test_target3)
392
+ c3.for.should.equal test_app
393
+ c3.app.should.equal test_target3
394
+ end
395
+
396
+ should "run different apps" do
397
+ c1 = Rack::Utils::Context.new test_app, test_target1
398
+ c2 = c1.recontext test_target2
399
+ c3 = c2.recontext test_target3
400
+ c4 = c3.recontext test_target4
401
+ a4 = Rack::Lint.new c4
402
+ a5 = Rack::Lint.new test_app
403
+ r1 = c1.call('hello')
404
+ r1.should.equal 'hello world'
405
+ r2 = c2.call(2)
406
+ r2.should.equal 4
407
+ r3 = c3.call(:misc_symbol)
408
+ r3.should.be.nil
409
+ r4 = Rack::MockRequest.new(a4).get('/')
410
+ r4.status.should.equal 200
411
+ r5 = Rack::MockRequest.new(a5).get('/')
412
+ r5.status.should.equal 200
413
+ r4.body.should.equal r5.body
414
+ end
415
+ end
416
+
417
+ describe Rack::Utils::Multipart do
418
+ def multipart_fixture(name)
419
+ file = multipart_file(name)
420
+ data = File.open(file, 'rb') { |io| io.read }
421
+
422
+ type = "multipart/form-data; boundary=AaB03x"
423
+ length = data.respond_to?(:bytesize) ? data.bytesize : data.size
424
+
425
+ { "CONTENT_TYPE" => type,
426
+ "CONTENT_LENGTH" => length.to_s,
427
+ :input => StringIO.new(data) }
428
+ end
429
+
430
+ def multipart_file(name)
431
+ File.join(File.dirname(__FILE__), "multipart", name.to_s)
432
+ end
433
+
434
+ should "return nil if content type is not multipart" do
435
+ env = Rack::MockRequest.env_for("/",
436
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded')
437
+ Rack::Utils::Multipart.parse_multipart(env).should.equal nil
438
+ end
439
+
440
+ should "parse multipart upload with text file" do
441
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
442
+ params = Rack::Utils::Multipart.parse_multipart(env)
443
+ params["submit-name"].should.equal "Larry"
444
+ params["submit-name-with-content"].should.equal "Berry"
445
+ params["files"][:type].should.equal "text/plain"
446
+ params["files"][:filename].should.equal "file1.txt"
447
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
448
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
449
+ "Content-Type: text/plain\r\n"
450
+ params["files"][:name].should.equal "files"
451
+ params["files"][:tempfile].read.should.equal "contents"
452
+ end
453
+
454
+ should "parse multipart upload with nested parameters" do
455
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
456
+ params = Rack::Utils::Multipart.parse_multipart(env)
457
+ params["foo"]["submit-name"].should.equal "Larry"
458
+ params["foo"]["files"][:type].should.equal "text/plain"
459
+ params["foo"]["files"][:filename].should.equal "file1.txt"
460
+ params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
461
+ "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
462
+ "Content-Type: text/plain\r\n"
463
+ params["foo"]["files"][:name].should.equal "foo[files]"
464
+ params["foo"]["files"][:tempfile].read.should.equal "contents"
465
+ end
466
+
467
+ should "parse multipart upload with binary file" do
468
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
469
+ params = Rack::Utils::Multipart.parse_multipart(env)
470
+ params["submit-name"].should.equal "Larry"
471
+ params["files"][:type].should.equal "image/png"
472
+ params["files"][:filename].should.equal "rack-logo.png"
473
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
474
+ "name=\"files\"; filename=\"rack-logo.png\"\r\n" +
475
+ "Content-Type: image/png\r\n"
476
+ params["files"][:name].should.equal "files"
477
+ params["files"][:tempfile].read.length.should.equal 26473
478
+ end
479
+
480
+ should "parse multipart upload with empty file" do
481
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
482
+ params = Rack::Utils::Multipart.parse_multipart(env)
483
+ params["submit-name"].should.equal "Larry"
484
+ params["files"][:type].should.equal "text/plain"
485
+ params["files"][:filename].should.equal "file1.txt"
486
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
487
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
488
+ "Content-Type: text/plain\r\n"
489
+ params["files"][:name].should.equal "files"
490
+ params["files"][:tempfile].read.should.equal ""
491
+ end
492
+
493
+ should "parse multipart upload with filename with semicolons" do
494
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon))
495
+ params = Rack::Utils::Multipart.parse_multipart(env)
496
+ params["files"][:type].should.equal "text/plain"
497
+ params["files"][:filename].should.equal "fi;le1.txt"
498
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
499
+ "name=\"files\"; filename=\"fi;le1.txt\"\r\n" +
500
+ "Content-Type: text/plain\r\n"
501
+ params["files"][:name].should.equal "files"
502
+ params["files"][:tempfile].read.should.equal "contents"
503
+ end
504
+
505
+ should "not include file params if no file was selected" do
506
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
507
+ params = Rack::Utils::Multipart.parse_multipart(env)
508
+ params["submit-name"].should.equal "Larry"
509
+ params["files"].should.equal nil
510
+ params.keys.should.not.include "files"
511
+ end
512
+
513
+ should "parse IE multipart upload and clean up filename" do
514
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
515
+ params = Rack::Utils::Multipart.parse_multipart(env)
516
+ params["files"][:type].should.equal "text/plain"
517
+ params["files"][:filename].should.equal "file1.txt"
518
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
519
+ "name=\"files\"; " +
520
+ 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
521
+ "\r\nContent-Type: text/plain\r\n"
522
+ params["files"][:name].should.equal "files"
523
+ params["files"][:tempfile].read.should.equal "contents"
524
+ end
525
+
526
+ should "parse filename and modification param" do
527
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param))
528
+ params = Rack::Utils::Multipart.parse_multipart(env)
529
+ params["files"][:type].should.equal "image/jpeg"
530
+ params["files"][:filename].should.equal "genome.jpeg"
531
+ params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
532
+ "Content-Disposition: attachment; " +
533
+ "name=\"files\"; " +
534
+ "filename=genome.jpeg; " +
535
+ "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
536
+ "Content-Description: a complete map of the human genome\r\n"
537
+ params["files"][:name].should.equal "files"
538
+ params["files"][:tempfile].read.should.equal "contents"
539
+ end
540
+
541
+ should "parse filename with escaped quotes" do
542
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes))
543
+ params = Rack::Utils::Multipart.parse_multipart(env)
544
+ params["files"][:type].should.equal "application/octet-stream"
545
+ params["files"][:filename].should.equal "escape \"quotes"
546
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
547
+ "name=\"files\"; " +
548
+ "filename=\"escape \\\"quotes\"\r\n" +
549
+ "Content-Type: application/octet-stream\r\n"
550
+ params["files"][:name].should.equal "files"
551
+ params["files"][:tempfile].read.should.equal "contents"
552
+ end
553
+
554
+ should "parse filename with percent escaped quotes" do
555
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes))
556
+ params = Rack::Utils::Multipart.parse_multipart(env)
557
+ params["files"][:type].should.equal "application/octet-stream"
558
+ params["files"][:filename].should.equal "escape \"quotes"
559
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
560
+ "name=\"files\"; " +
561
+ "filename=\"escape %22quotes\"\r\n" +
562
+ "Content-Type: application/octet-stream\r\n"
563
+ params["files"][:name].should.equal "files"
564
+ params["files"][:tempfile].read.should.equal "contents"
565
+ end
566
+
567
+ should "parse filename with unescaped quotes" do
568
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes))
569
+ params = Rack::Utils::Multipart.parse_multipart(env)
570
+ params["files"][:type].should.equal "application/octet-stream"
571
+ params["files"][:filename].should.equal "escape \"quotes"
572
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
573
+ "name=\"files\"; " +
574
+ "filename=\"escape \"quotes\"\r\n" +
575
+ "Content-Type: application/octet-stream\r\n"
576
+ params["files"][:name].should.equal "files"
577
+ params["files"][:tempfile].read.should.equal "contents"
578
+ end
579
+
580
+ should "parse filename with escaped quotes and modification param" do
581
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param))
582
+ params = Rack::Utils::Multipart.parse_multipart(env)
583
+ params["files"][:type].should.equal "image/jpeg"
584
+ params["files"][:filename].should.equal "\"human\" genome.jpeg"
585
+ params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
586
+ "Content-Disposition: attachment; " +
587
+ "name=\"files\"; " +
588
+ "filename=\"\"human\" genome.jpeg\"; " +
589
+ "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
590
+ "Content-Description: a complete map of the human genome\r\n"
591
+ params["files"][:name].should.equal "files"
592
+ params["files"][:tempfile].read.should.equal "contents"
593
+ end
594
+
595
+ it "rewinds input after parsing upload" do
596
+ options = multipart_fixture(:text)
597
+ input = options[:input]
598
+ env = Rack::MockRequest.env_for("/", options)
599
+ params = Rack::Utils::Multipart.parse_multipart(env)
600
+ params["submit-name"].should.equal "Larry"
601
+ params["files"][:filename].should.equal "file1.txt"
602
+ input.read.length.should.equal 307
603
+ end
604
+
605
+ it "builds multipart body" do
606
+ files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
607
+ data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
608
+
609
+ options = {
610
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
611
+ "CONTENT_LENGTH" => data.length.to_s,
612
+ :input => StringIO.new(data)
613
+ }
614
+ env = Rack::MockRequest.env_for("/", options)
615
+ params = Rack::Utils::Multipart.parse_multipart(env)
616
+ params["submit-name"].should.equal "Larry"
617
+ params["files"][:filename].should.equal "file1.txt"
618
+ params["files"][:tempfile].read.should.equal "contents"
619
+ end
620
+
621
+ it "builds nested multipart body" do
622
+ files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
623
+ data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
624
+
625
+ options = {
626
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
627
+ "CONTENT_LENGTH" => data.length.to_s,
628
+ :input => StringIO.new(data)
629
+ }
630
+ env = Rack::MockRequest.env_for("/", options)
631
+ params = Rack::Utils::Multipart.parse_multipart(env)
632
+ params["people"][0]["submit-name"].should.equal "Larry"
633
+ params["people"][0]["files"][:filename].should.equal "file1.txt"
634
+ params["people"][0]["files"][:tempfile].read.should.equal "contents"
635
+ end
636
+
637
+ it "can parse fields that end at the end of the buffer" do
638
+ input = File.read(multipart_file("bad_robots"))
639
+
640
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
641
+ "CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
642
+ "CONTENT_LENGTH" => input.size,
643
+ :input => input)
644
+
645
+ req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
646
+ req.POST['addresses'].should.not.equal nil
647
+ end
648
+
649
+ it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
650
+ data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
651
+ options = {
652
+ "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
653
+ "CONTENT_LENGTH" => data.length.to_s,
654
+ :input => StringIO.new(data)
655
+ }
656
+ env = Rack::MockRequest.env_for("/", options)
657
+ params = Rack::Utils::Multipart.parse_multipart(env)
658
+
659
+ params.should.not.equal nil
660
+ params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
661
+ params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
662
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
663
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
664
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
665
+ params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
666
+ end
667
+
668
+ should "return nil if no UploadedFiles were used" do
669
+ data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
670
+ data.should.equal nil
671
+ end
672
+
673
+ should "raise ArgumentError if params is not a Hash" do
674
+ lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }.
675
+ should.raise(ArgumentError).
676
+ message.should.equal "value must be a Hash"
677
+ end
678
+ end