edgar-rack 1.2.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 (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