lack 2.0.0
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.
- checksums.yaml +7 -0
- data/bin/rackup +5 -0
- data/lib/rack.rb +26 -0
- data/lib/rack/body_proxy.rb +39 -0
- data/lib/rack/builder.rb +166 -0
- data/lib/rack/handler.rb +63 -0
- data/lib/rack/handler/webrick.rb +120 -0
- data/lib/rack/mime.rb +661 -0
- data/lib/rack/mock.rb +198 -0
- data/lib/rack/multipart.rb +31 -0
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +239 -0
- data/lib/rack/multipart/uploaded_file.rb +34 -0
- data/lib/rack/request.rb +394 -0
- data/lib/rack/response.rb +160 -0
- data/lib/rack/server.rb +258 -0
- data/lib/rack/server/options.rb +121 -0
- data/lib/rack/utils.rb +653 -0
- data/lib/rack/version.rb +3 -0
- data/spec/spec_helper.rb +1 -0
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +5 -0
- data/test/builder/line.ru +1 -0
- data/test/builder/options.ru +2 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_percentages +6 -0
- data/test/multipart/filename_with_unescaped_percentages2 +6 -0
- data/test/multipart/filename_with_unescaped_percentages3 +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/multipart/mixed_files +21 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/multipart/webkit +32 -0
- data/test/rackup/config.ru +31 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/spec_body_proxy.rb +69 -0
- data/test/spec_builder.rb +223 -0
- data/test/spec_chunked.rb +101 -0
- data/test/spec_file.rb +221 -0
- data/test/spec_handler.rb +59 -0
- data/test/spec_head.rb +45 -0
- data/test/spec_lint.rb +522 -0
- data/test/spec_mime.rb +51 -0
- data/test/spec_mock.rb +277 -0
- data/test/spec_multipart.rb +547 -0
- data/test/spec_recursive.rb +72 -0
- data/test/spec_request.rb +1199 -0
- data/test/spec_response.rb +343 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_sendfile.rb +130 -0
- data/test/spec_server.rb +167 -0
- data/test/spec_utils.rb +635 -0
- data/test/spec_webrick.rb +184 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +240 -0
data/test/spec_mime.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rack/mime'
|
2
|
+
|
3
|
+
describe Rack::Mime do
|
4
|
+
|
5
|
+
it "should return the fallback mime-type for files with no extension" do
|
6
|
+
fallback = 'image/jpg'
|
7
|
+
Rack::Mime.mime_type(File.extname('no_ext'), fallback).should.equal fallback
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should always return 'application/octet-stream' for unknown file extensions" do
|
11
|
+
unknown_ext = File.extname('unknown_ext.abcdefg')
|
12
|
+
Rack::Mime.mime_type(unknown_ext).should.equal 'application/octet-stream'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return the mime-type for a given extension" do
|
16
|
+
# sanity check. it would be infeasible test every single mime-type.
|
17
|
+
Rack::Mime.mime_type(File.extname('image.jpg')).should.equal 'image/jpeg'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should support null fallbacks" do
|
21
|
+
Rack::Mime.mime_type('.nothing', nil).should.equal nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should match exact mimes" do
|
25
|
+
Rack::Mime.match?('text/html', 'text/html').should.equal true
|
26
|
+
Rack::Mime.match?('text/html', 'text/meme').should.equal false
|
27
|
+
Rack::Mime.match?('text', 'text').should.equal true
|
28
|
+
Rack::Mime.match?('text', 'binary').should.equal false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should match class wildcard mimes" do
|
32
|
+
Rack::Mime.match?('text/html', 'text/*').should.equal true
|
33
|
+
Rack::Mime.match?('text/plain', 'text/*').should.equal true
|
34
|
+
Rack::Mime.match?('application/json', 'text/*').should.equal false
|
35
|
+
Rack::Mime.match?('text/html', 'text').should.equal true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should match full wildcards" do
|
39
|
+
Rack::Mime.match?('text/html', '*').should.equal true
|
40
|
+
Rack::Mime.match?('text/plain', '*').should.equal true
|
41
|
+
Rack::Mime.match?('text/html', '*/*').should.equal true
|
42
|
+
Rack::Mime.match?('text/plain', '*/*').should.equal true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should match type wildcard mimes" do
|
46
|
+
Rack::Mime.match?('text/html', '*/html').should.equal true
|
47
|
+
Rack::Mime.match?('text/plain', '*/plain').should.equal true
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
data/test/spec_mock.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'rack/lint'
|
3
|
+
require 'rack/mock'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
app = Rack::Lint.new(lambda { |env|
|
7
|
+
req = Rack::Request.new(env)
|
8
|
+
|
9
|
+
env["mock.postdata"] = env["rack.input"].read
|
10
|
+
if req.GET["error"]
|
11
|
+
env["rack.errors"].puts req.GET["error"]
|
12
|
+
env["rack.errors"].flush
|
13
|
+
end
|
14
|
+
|
15
|
+
body = req.head? ? "" : env.to_yaml
|
16
|
+
Rack::Response.new(body,
|
17
|
+
req.GET["status"] || 200,
|
18
|
+
"Content-Type" => "text/yaml").finish
|
19
|
+
})
|
20
|
+
|
21
|
+
describe Rack::MockRequest do
|
22
|
+
should "return a MockResponse" do
|
23
|
+
res = Rack::MockRequest.new(app).get("")
|
24
|
+
res.should.be.kind_of Rack::MockResponse
|
25
|
+
end
|
26
|
+
|
27
|
+
should "be able to only return the environment" do
|
28
|
+
env = Rack::MockRequest.env_for("")
|
29
|
+
env.should.be.kind_of Hash
|
30
|
+
env.should.include "rack.version"
|
31
|
+
end
|
32
|
+
|
33
|
+
should "return an environment with a path" do
|
34
|
+
env = Rack::MockRequest.env_for("http://www.example.com/parse?location[]=1&location[]=2&age_group[]=2")
|
35
|
+
env["QUERY_STRING"].should.equal "location[]=1&location[]=2&age_group[]=2"
|
36
|
+
env["PATH_INFO"].should.equal "/parse"
|
37
|
+
env.should.be.kind_of Hash
|
38
|
+
env.should.include "rack.version"
|
39
|
+
end
|
40
|
+
|
41
|
+
should "provide sensible defaults" do
|
42
|
+
res = Rack::MockRequest.new(app).request
|
43
|
+
|
44
|
+
env = YAML.load(res.body)
|
45
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
46
|
+
env["SERVER_NAME"].should.equal "example.org"
|
47
|
+
env["SERVER_PORT"].should.equal "80"
|
48
|
+
env["QUERY_STRING"].should.equal ""
|
49
|
+
env["PATH_INFO"].should.equal "/"
|
50
|
+
env["SCRIPT_NAME"].should.equal ""
|
51
|
+
env["rack.url_scheme"].should.equal "http"
|
52
|
+
env["mock.postdata"].should.be.empty
|
53
|
+
end
|
54
|
+
|
55
|
+
should "allow GET/POST/PUT/DELETE/HEAD" do
|
56
|
+
res = Rack::MockRequest.new(app).get("", :input => "foo")
|
57
|
+
env = YAML.load(res.body)
|
58
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
59
|
+
|
60
|
+
res = Rack::MockRequest.new(app).post("", :input => "foo")
|
61
|
+
env = YAML.load(res.body)
|
62
|
+
env["REQUEST_METHOD"].should.equal "POST"
|
63
|
+
|
64
|
+
res = Rack::MockRequest.new(app).put("", :input => "foo")
|
65
|
+
env = YAML.load(res.body)
|
66
|
+
env["REQUEST_METHOD"].should.equal "PUT"
|
67
|
+
|
68
|
+
res = Rack::MockRequest.new(app).patch("", :input => "foo")
|
69
|
+
env = YAML.load(res.body)
|
70
|
+
env["REQUEST_METHOD"].should.equal "PATCH"
|
71
|
+
|
72
|
+
res = Rack::MockRequest.new(app).delete("", :input => "foo")
|
73
|
+
env = YAML.load(res.body)
|
74
|
+
env["REQUEST_METHOD"].should.equal "DELETE"
|
75
|
+
|
76
|
+
Rack::MockRequest.env_for("/", :method => "HEAD")["REQUEST_METHOD"].
|
77
|
+
should.equal "HEAD"
|
78
|
+
|
79
|
+
Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"].
|
80
|
+
should.equal "OPTIONS"
|
81
|
+
end
|
82
|
+
|
83
|
+
should "set content length" do
|
84
|
+
env = Rack::MockRequest.env_for("/", :input => "foo")
|
85
|
+
env["CONTENT_LENGTH"].should.equal "3"
|
86
|
+
end
|
87
|
+
|
88
|
+
should "allow posting" do
|
89
|
+
res = Rack::MockRequest.new(app).get("", :input => "foo")
|
90
|
+
env = YAML.load(res.body)
|
91
|
+
env["mock.postdata"].should.equal "foo"
|
92
|
+
|
93
|
+
res = Rack::MockRequest.new(app).post("", :input => StringIO.new("foo"))
|
94
|
+
env = YAML.load(res.body)
|
95
|
+
env["mock.postdata"].should.equal "foo"
|
96
|
+
end
|
97
|
+
|
98
|
+
should "use all parts of an URL" do
|
99
|
+
res = Rack::MockRequest.new(app).
|
100
|
+
get("https://bla.example.org:9292/meh/foo?bar")
|
101
|
+
res.should.be.kind_of Rack::MockResponse
|
102
|
+
|
103
|
+
env = YAML.load(res.body)
|
104
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
105
|
+
env["SERVER_NAME"].should.equal "bla.example.org"
|
106
|
+
env["SERVER_PORT"].should.equal "9292"
|
107
|
+
env["QUERY_STRING"].should.equal "bar"
|
108
|
+
env["PATH_INFO"].should.equal "/meh/foo"
|
109
|
+
env["rack.url_scheme"].should.equal "https"
|
110
|
+
end
|
111
|
+
|
112
|
+
should "set SSL port and HTTP flag on when using https" do
|
113
|
+
res = Rack::MockRequest.new(app).
|
114
|
+
get("https://example.org/foo")
|
115
|
+
res.should.be.kind_of Rack::MockResponse
|
116
|
+
|
117
|
+
env = YAML.load(res.body)
|
118
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
119
|
+
env["SERVER_NAME"].should.equal "example.org"
|
120
|
+
env["SERVER_PORT"].should.equal "443"
|
121
|
+
env["QUERY_STRING"].should.equal ""
|
122
|
+
env["PATH_INFO"].should.equal "/foo"
|
123
|
+
env["rack.url_scheme"].should.equal "https"
|
124
|
+
env["HTTPS"].should.equal "on"
|
125
|
+
end
|
126
|
+
|
127
|
+
should "prepend slash to uri path" do
|
128
|
+
res = Rack::MockRequest.new(app).
|
129
|
+
get("foo")
|
130
|
+
res.should.be.kind_of Rack::MockResponse
|
131
|
+
|
132
|
+
env = YAML.load(res.body)
|
133
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
134
|
+
env["SERVER_NAME"].should.equal "example.org"
|
135
|
+
env["SERVER_PORT"].should.equal "80"
|
136
|
+
env["QUERY_STRING"].should.equal ""
|
137
|
+
env["PATH_INFO"].should.equal "/foo"
|
138
|
+
env["rack.url_scheme"].should.equal "http"
|
139
|
+
end
|
140
|
+
|
141
|
+
should "properly convert method name to an uppercase string" do
|
142
|
+
res = Rack::MockRequest.new(app).request(:get)
|
143
|
+
env = YAML.load(res.body)
|
144
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
145
|
+
end
|
146
|
+
|
147
|
+
should "accept params and build query string for GET requests" do
|
148
|
+
res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => {:foo => {:bar => "1"}})
|
149
|
+
env = YAML.load(res.body)
|
150
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
151
|
+
env["QUERY_STRING"].should.include "baz=2"
|
152
|
+
env["QUERY_STRING"].should.include "foo[bar]=1"
|
153
|
+
env["PATH_INFO"].should.equal "/foo"
|
154
|
+
env["mock.postdata"].should.equal ""
|
155
|
+
end
|
156
|
+
|
157
|
+
should "accept raw input in params for GET requests" do
|
158
|
+
res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => "foo[bar]=1")
|
159
|
+
env = YAML.load(res.body)
|
160
|
+
env["REQUEST_METHOD"].should.equal "GET"
|
161
|
+
env["QUERY_STRING"].should.include "baz=2"
|
162
|
+
env["QUERY_STRING"].should.include "foo[bar]=1"
|
163
|
+
env["PATH_INFO"].should.equal "/foo"
|
164
|
+
env["mock.postdata"].should.equal ""
|
165
|
+
end
|
166
|
+
|
167
|
+
should "accept params and build url encoded params for POST requests" do
|
168
|
+
res = Rack::MockRequest.new(app).post("/foo", :params => {:foo => {:bar => "1"}})
|
169
|
+
env = YAML.load(res.body)
|
170
|
+
env["REQUEST_METHOD"].should.equal "POST"
|
171
|
+
env["QUERY_STRING"].should.equal ""
|
172
|
+
env["PATH_INFO"].should.equal "/foo"
|
173
|
+
env["CONTENT_TYPE"].should.equal "application/x-www-form-urlencoded"
|
174
|
+
env["mock.postdata"].should.equal "foo[bar]=1"
|
175
|
+
end
|
176
|
+
|
177
|
+
should "accept raw input in params for POST requests" do
|
178
|
+
res = Rack::MockRequest.new(app).post("/foo", :params => "foo[bar]=1")
|
179
|
+
env = YAML.load(res.body)
|
180
|
+
env["REQUEST_METHOD"].should.equal "POST"
|
181
|
+
env["QUERY_STRING"].should.equal ""
|
182
|
+
env["PATH_INFO"].should.equal "/foo"
|
183
|
+
env["CONTENT_TYPE"].should.equal "application/x-www-form-urlencoded"
|
184
|
+
env["mock.postdata"].should.equal "foo[bar]=1"
|
185
|
+
end
|
186
|
+
|
187
|
+
should "accept params and build multipart encoded params for POST requests" do
|
188
|
+
files = Rack::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt"))
|
189
|
+
res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files })
|
190
|
+
env = YAML.load(res.body)
|
191
|
+
env["REQUEST_METHOD"].should.equal "POST"
|
192
|
+
env["QUERY_STRING"].should.equal ""
|
193
|
+
env["PATH_INFO"].should.equal "/foo"
|
194
|
+
env["CONTENT_TYPE"].should.equal "multipart/form-data; boundary=AaB03x"
|
195
|
+
# The gsub accounts for differences in YAMLs affect on the data.
|
196
|
+
env["mock.postdata"].gsub("\r", "").length.should.equal 206
|
197
|
+
end
|
198
|
+
|
199
|
+
should "behave valid according to the Rack spec" do
|
200
|
+
lambda {
|
201
|
+
Rack::MockRequest.new(app).
|
202
|
+
get("https://bla.example.org:9292/meh/foo?bar", :lint => true)
|
203
|
+
}.should.not.raise(Rack::Lint::LintError)
|
204
|
+
end
|
205
|
+
|
206
|
+
should "call close on the original body object" do
|
207
|
+
called = false
|
208
|
+
body = Rack::BodyProxy.new(['hi']) { called = true }
|
209
|
+
capp = proc { |e| [200, {'Content-Type' => 'text/plain'}, body] }
|
210
|
+
called.should.equal false
|
211
|
+
Rack::MockRequest.new(capp).get('/', :lint => true)
|
212
|
+
called.should.equal true
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe Rack::MockResponse do
|
217
|
+
should "provide access to the HTTP status" do
|
218
|
+
res = Rack::MockRequest.new(app).get("")
|
219
|
+
res.should.be.successful
|
220
|
+
res.should.be.ok
|
221
|
+
|
222
|
+
res = Rack::MockRequest.new(app).get("/?status=404")
|
223
|
+
res.should.not.be.successful
|
224
|
+
res.should.be.client_error
|
225
|
+
res.should.be.not_found
|
226
|
+
|
227
|
+
res = Rack::MockRequest.new(app).get("/?status=501")
|
228
|
+
res.should.not.be.successful
|
229
|
+
res.should.be.server_error
|
230
|
+
|
231
|
+
res = Rack::MockRequest.new(app).get("/?status=307")
|
232
|
+
res.should.be.redirect
|
233
|
+
|
234
|
+
res = Rack::MockRequest.new(app).get("/?status=201", :lint => true)
|
235
|
+
res.should.be.empty
|
236
|
+
end
|
237
|
+
|
238
|
+
should "provide access to the HTTP headers" do
|
239
|
+
res = Rack::MockRequest.new(app).get("")
|
240
|
+
res.should.include "Content-Type"
|
241
|
+
res.headers["Content-Type"].should.equal "text/yaml"
|
242
|
+
res.original_headers["Content-Type"].should.equal "text/yaml"
|
243
|
+
res["Content-Type"].should.equal "text/yaml"
|
244
|
+
res.content_type.should.equal "text/yaml"
|
245
|
+
res.content_length.should.not.equal 0
|
246
|
+
res.location.should.be.nil
|
247
|
+
end
|
248
|
+
|
249
|
+
should "provide access to the HTTP body" do
|
250
|
+
res = Rack::MockRequest.new(app).get("")
|
251
|
+
res.body.should =~ /rack/
|
252
|
+
res.should =~ /rack/
|
253
|
+
res.should.match(/rack/)
|
254
|
+
res.should.satisfy { |r| r.match(/rack/) }
|
255
|
+
end
|
256
|
+
|
257
|
+
should "provide access to the Rack errors" do
|
258
|
+
res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true)
|
259
|
+
res.should.be.ok
|
260
|
+
res.errors.should.not.be.empty
|
261
|
+
res.errors.should.include "foo"
|
262
|
+
end
|
263
|
+
|
264
|
+
should "allow calling body.close afterwards" do
|
265
|
+
# this is exactly what rack-test does
|
266
|
+
body = StringIO.new("hi")
|
267
|
+
res = Rack::MockResponse.new(200, {}, body)
|
268
|
+
body.close if body.respond_to?(:close)
|
269
|
+
res.body.should == 'hi'
|
270
|
+
end
|
271
|
+
|
272
|
+
should "optionally make Rack errors fatal" do
|
273
|
+
lambda {
|
274
|
+
Rack::MockRequest.new(app).get("/?error=foo", :fatal => true)
|
275
|
+
}.should.raise(Rack::MockRequest::FatalWarning)
|
276
|
+
end
|
277
|
+
end
|
@@ -0,0 +1,547 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
require 'rack/mock'
|
3
|
+
|
4
|
+
describe Rack::Multipart do
|
5
|
+
def multipart_fixture(name, boundary = "AaB03x")
|
6
|
+
file = multipart_file(name)
|
7
|
+
data = File.open(file, 'rb') { |io| io.read }
|
8
|
+
|
9
|
+
type = "multipart/form-data; boundary=#{boundary}"
|
10
|
+
length = data.respond_to?(:bytesize) ? data.bytesize : data.size
|
11
|
+
|
12
|
+
{ "CONTENT_TYPE" => type,
|
13
|
+
"CONTENT_LENGTH" => length.to_s,
|
14
|
+
:input => StringIO.new(data) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def multipart_file(name)
|
18
|
+
File.join(File.dirname(__FILE__), "multipart", name.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "return nil if content type is not multipart" do
|
22
|
+
env = Rack::MockRequest.env_for("/",
|
23
|
+
"CONTENT_TYPE" => 'application/x-www-form-urlencoded')
|
24
|
+
Rack::Multipart.parse_multipart(env).should.equal nil
|
25
|
+
end
|
26
|
+
|
27
|
+
should "parse multipart content when content type present but filename is not" do
|
28
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
|
29
|
+
params = Rack::Multipart.parse_multipart(env)
|
30
|
+
params["text"].should.equal "contents"
|
31
|
+
end
|
32
|
+
|
33
|
+
if "<3".respond_to?(:force_encoding)
|
34
|
+
should "set US_ASCII encoding based on charset" do
|
35
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
|
36
|
+
params = Rack::Multipart.parse_multipart(env)
|
37
|
+
params["text"].encoding.should.equal Encoding::US_ASCII
|
38
|
+
|
39
|
+
# I'm not 100% sure if making the param name encoding match the
|
40
|
+
# Content-Type charset is the right thing to do. We should revisit this.
|
41
|
+
params.keys.each do |key|
|
42
|
+
key.encoding.should.equal Encoding::US_ASCII
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
should "set BINARY encoding on things without content type" do
|
47
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
48
|
+
params = Rack::Multipart.parse_multipart(env)
|
49
|
+
params["submit-name"].encoding.should.equal Encoding::UTF_8
|
50
|
+
end
|
51
|
+
|
52
|
+
should "set UTF8 encoding on names of things without content type" do
|
53
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
54
|
+
params = Rack::Multipart.parse_multipart(env)
|
55
|
+
params.keys.each do |key|
|
56
|
+
key.encoding.should.equal Encoding::UTF_8
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
should "default text to UTF8" do
|
61
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
|
62
|
+
params = Rack::Multipart.parse_multipart(env)
|
63
|
+
params['submit-name'].encoding.should.equal Encoding::UTF_8
|
64
|
+
params['submit-name-with-content'].encoding.should.equal Encoding::UTF_8
|
65
|
+
params.keys.each do |key|
|
66
|
+
key.encoding.should.equal Encoding::UTF_8
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
should "raise RangeError if the key space is exhausted" do
|
72
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
|
73
|
+
|
74
|
+
old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
|
75
|
+
begin
|
76
|
+
lambda { Rack::Multipart.parse_multipart(env) }.should.raise(RangeError)
|
77
|
+
ensure
|
78
|
+
Rack::Utils.key_space_limit = old
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
should "parse multipart form webkit style" do
|
83
|
+
env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit)
|
84
|
+
env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
|
85
|
+
params = Rack::Multipart.parse_multipart(env)
|
86
|
+
params['profile']['bio'].should.include 'hello'
|
87
|
+
end
|
88
|
+
|
89
|
+
should "reject insanely long boundaries" do
|
90
|
+
# using a pipe since a tempfile can use up too much space
|
91
|
+
rd, wr = IO.pipe
|
92
|
+
|
93
|
+
# we only call rewind once at start, so make sure it succeeds
|
94
|
+
# and doesn't hit ESPIPE
|
95
|
+
def rd.rewind; end
|
96
|
+
wr.sync = true
|
97
|
+
|
98
|
+
# mock out length to make this pipe look like a Tempfile
|
99
|
+
def rd.length
|
100
|
+
1024 * 1024 * 8
|
101
|
+
end
|
102
|
+
|
103
|
+
# write to a pipe in a background thread, this will write a lot
|
104
|
+
# unless Rack (properly) shuts down the read end
|
105
|
+
thr = Thread.new do
|
106
|
+
begin
|
107
|
+
wr.write("--AaB03x")
|
108
|
+
|
109
|
+
# make the initial boundary a few gigs long
|
110
|
+
longer = "0123456789" * 1024 * 1024
|
111
|
+
(1024 * 1024).times { wr.write(longer) }
|
112
|
+
|
113
|
+
wr.write("\r\n")
|
114
|
+
wr.write('Content-Disposition: form-data; name="a"; filename="a.txt"')
|
115
|
+
wr.write("\r\n")
|
116
|
+
wr.write("Content-Type: text/plain\r\n")
|
117
|
+
wr.write("\r\na")
|
118
|
+
wr.write("--AaB03x--\r\n")
|
119
|
+
wr.close
|
120
|
+
rescue => err # this is EPIPE if Rack shuts us down
|
121
|
+
err
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
fixture = {
|
126
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
127
|
+
"CONTENT_LENGTH" => rd.length.to_s,
|
128
|
+
:input => rd,
|
129
|
+
}
|
130
|
+
|
131
|
+
env = Rack::MockRequest.env_for '/', fixture
|
132
|
+
lambda {
|
133
|
+
Rack::Multipart.parse_multipart(env)
|
134
|
+
}.should.raise(EOFError)
|
135
|
+
rd.close
|
136
|
+
|
137
|
+
err = thr.value
|
138
|
+
err.should.be.instance_of Errno::EPIPE
|
139
|
+
wr.close
|
140
|
+
end
|
141
|
+
|
142
|
+
should "parse multipart upload with text file" do
|
143
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
|
144
|
+
params = Rack::Multipart.parse_multipart(env)
|
145
|
+
params["submit-name"].should.equal "Larry"
|
146
|
+
params["submit-name-with-content"].should.equal "Berry"
|
147
|
+
params["files"][:type].should.equal "text/plain"
|
148
|
+
params["files"][:filename].should.equal "file1.txt"
|
149
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
150
|
+
"name=\"files\"; filename=\"file1.txt\"\r\n" +
|
151
|
+
"Content-Type: text/plain\r\n"
|
152
|
+
params["files"][:name].should.equal "files"
|
153
|
+
params["files"][:tempfile].read.should.equal "contents"
|
154
|
+
end
|
155
|
+
|
156
|
+
should "preserve extension in the created tempfile" do
|
157
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
|
158
|
+
params = Rack::Multipart.parse_multipart(env)
|
159
|
+
File.extname(params["files"][:tempfile].path).should.equal ".txt"
|
160
|
+
end
|
161
|
+
|
162
|
+
should "parse multipart upload with text file with no name field" do
|
163
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_no_name))
|
164
|
+
params = Rack::Multipart.parse_multipart(env)
|
165
|
+
params["file1.txt"][:type].should.equal "text/plain"
|
166
|
+
params["file1.txt"][:filename].should.equal "file1.txt"
|
167
|
+
params["file1.txt"][:head].should.equal "Content-Disposition: form-data; " +
|
168
|
+
"filename=\"file1.txt\"\r\n" +
|
169
|
+
"Content-Type: text/plain\r\n"
|
170
|
+
params["file1.txt"][:name].should.equal "file1.txt"
|
171
|
+
params["file1.txt"][:tempfile].read.should.equal "contents"
|
172
|
+
end
|
173
|
+
|
174
|
+
should "parse multipart upload with nested parameters" do
|
175
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
|
176
|
+
params = Rack::Multipart.parse_multipart(env)
|
177
|
+
params["foo"]["submit-name"].should.equal "Larry"
|
178
|
+
params["foo"]["files"][:type].should.equal "text/plain"
|
179
|
+
params["foo"]["files"][:filename].should.equal "file1.txt"
|
180
|
+
params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
|
181
|
+
"name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
|
182
|
+
"Content-Type: text/plain\r\n"
|
183
|
+
params["foo"]["files"][:name].should.equal "foo[files]"
|
184
|
+
params["foo"]["files"][:tempfile].read.should.equal "contents"
|
185
|
+
end
|
186
|
+
|
187
|
+
should "parse multipart upload with binary file" do
|
188
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
|
189
|
+
params = Rack::Multipart.parse_multipart(env)
|
190
|
+
params["submit-name"].should.equal "Larry"
|
191
|
+
params["files"][:type].should.equal "image/png"
|
192
|
+
params["files"][:filename].should.equal "rack-logo.png"
|
193
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
194
|
+
"name=\"files\"; filename=\"rack-logo.png\"\r\n" +
|
195
|
+
"Content-Type: image/png\r\n"
|
196
|
+
params["files"][:name].should.equal "files"
|
197
|
+
params["files"][:tempfile].read.length.should.equal 26473
|
198
|
+
end
|
199
|
+
|
200
|
+
should "parse multipart upload with empty file" do
|
201
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
|
202
|
+
params = Rack::Multipart.parse_multipart(env)
|
203
|
+
params["submit-name"].should.equal "Larry"
|
204
|
+
params["files"][:type].should.equal "text/plain"
|
205
|
+
params["files"][:filename].should.equal "file1.txt"
|
206
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
207
|
+
"name=\"files\"; filename=\"file1.txt\"\r\n" +
|
208
|
+
"Content-Type: text/plain\r\n"
|
209
|
+
params["files"][:name].should.equal "files"
|
210
|
+
params["files"][:tempfile].read.should.equal ""
|
211
|
+
end
|
212
|
+
|
213
|
+
should "parse multipart upload with filename with semicolons" do
|
214
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon))
|
215
|
+
params = Rack::Multipart.parse_multipart(env)
|
216
|
+
params["files"][:type].should.equal "text/plain"
|
217
|
+
params["files"][:filename].should.equal "fi;le1.txt"
|
218
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
219
|
+
"name=\"files\"; filename=\"fi;le1.txt\"\r\n" +
|
220
|
+
"Content-Type: text/plain\r\n"
|
221
|
+
params["files"][:name].should.equal "files"
|
222
|
+
params["files"][:tempfile].read.should.equal "contents"
|
223
|
+
end
|
224
|
+
|
225
|
+
should "parse multipart upload with filename with invalid characters" do
|
226
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:invalid_character))
|
227
|
+
params = Rack::Multipart.parse_multipart(env)
|
228
|
+
params["files"][:type].should.equal "text/plain"
|
229
|
+
params["files"][:filename].should.match(/invalid/)
|
230
|
+
head = "Content-Disposition: form-data; " +
|
231
|
+
"name=\"files\"; filename=\"invalid\xC3.txt\"\r\n" +
|
232
|
+
"Content-Type: text/plain\r\n"
|
233
|
+
head = head.force_encoding("ASCII-8BIT") if head.respond_to?(:force_encoding)
|
234
|
+
params["files"][:head].should.equal head
|
235
|
+
params["files"][:name].should.equal "files"
|
236
|
+
params["files"][:tempfile].read.should.equal "contents"
|
237
|
+
end
|
238
|
+
|
239
|
+
should "not include file params if no file was selected" do
|
240
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
241
|
+
params = Rack::Multipart.parse_multipart(env)
|
242
|
+
params["submit-name"].should.equal "Larry"
|
243
|
+
params["files"].should.equal nil
|
244
|
+
params.keys.should.not.include "files"
|
245
|
+
end
|
246
|
+
|
247
|
+
should "parse multipart/mixed" do
|
248
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files))
|
249
|
+
params = Rack::Utils::Multipart.parse_multipart(env)
|
250
|
+
params["foo"].should.equal "bar"
|
251
|
+
params["files"].should.be.instance_of String
|
252
|
+
params["files"].size.should.equal 252
|
253
|
+
end
|
254
|
+
|
255
|
+
should "parse multipart/mixed" do
|
256
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files))
|
257
|
+
params = Rack::Utils::Multipart.parse_multipart(env)
|
258
|
+
params["foo"].should.equal "bar"
|
259
|
+
params["files"].should.be.instance_of String
|
260
|
+
params["files"].size.should.equal 252
|
261
|
+
end
|
262
|
+
|
263
|
+
should "parse IE multipart upload and clean up filename" do
|
264
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
|
265
|
+
params = Rack::Multipart.parse_multipart(env)
|
266
|
+
params["files"][:type].should.equal "text/plain"
|
267
|
+
params["files"][:filename].should.equal "file1.txt"
|
268
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
269
|
+
"name=\"files\"; " +
|
270
|
+
'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
|
271
|
+
"\r\nContent-Type: text/plain\r\n"
|
272
|
+
params["files"][:name].should.equal "files"
|
273
|
+
params["files"][:tempfile].read.should.equal "contents"
|
274
|
+
end
|
275
|
+
|
276
|
+
should "parse filename and modification param" do
|
277
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param))
|
278
|
+
params = Rack::Multipart.parse_multipart(env)
|
279
|
+
params["files"][:type].should.equal "image/jpeg"
|
280
|
+
params["files"][:filename].should.equal "genome.jpeg"
|
281
|
+
params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
|
282
|
+
"Content-Disposition: attachment; " +
|
283
|
+
"name=\"files\"; " +
|
284
|
+
"filename=genome.jpeg; " +
|
285
|
+
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
|
286
|
+
"Content-Description: a complete map of the human genome\r\n"
|
287
|
+
params["files"][:name].should.equal "files"
|
288
|
+
params["files"][:tempfile].read.should.equal "contents"
|
289
|
+
end
|
290
|
+
|
291
|
+
should "parse filename with escaped quotes" do
|
292
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes))
|
293
|
+
params = Rack::Multipart.parse_multipart(env)
|
294
|
+
params["files"][:type].should.equal "application/octet-stream"
|
295
|
+
params["files"][:filename].should.equal "escape \"quotes"
|
296
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
297
|
+
"name=\"files\"; " +
|
298
|
+
"filename=\"escape \\\"quotes\"\r\n" +
|
299
|
+
"Content-Type: application/octet-stream\r\n"
|
300
|
+
params["files"][:name].should.equal "files"
|
301
|
+
params["files"][:tempfile].read.should.equal "contents"
|
302
|
+
end
|
303
|
+
|
304
|
+
should "parse filename with percent escaped quotes" do
|
305
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes))
|
306
|
+
params = Rack::Multipart.parse_multipart(env)
|
307
|
+
params["files"][:type].should.equal "application/octet-stream"
|
308
|
+
params["files"][:filename].should.equal "escape \"quotes"
|
309
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
310
|
+
"name=\"files\"; " +
|
311
|
+
"filename=\"escape %22quotes\"\r\n" +
|
312
|
+
"Content-Type: application/octet-stream\r\n"
|
313
|
+
params["files"][:name].should.equal "files"
|
314
|
+
params["files"][:tempfile].read.should.equal "contents"
|
315
|
+
end
|
316
|
+
|
317
|
+
should "parse filename with unescaped quotes" do
|
318
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes))
|
319
|
+
params = Rack::Multipart.parse_multipart(env)
|
320
|
+
params["files"][:type].should.equal "application/octet-stream"
|
321
|
+
params["files"][:filename].should.equal "escape \"quotes"
|
322
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
323
|
+
"name=\"files\"; " +
|
324
|
+
"filename=\"escape \"quotes\"\r\n" +
|
325
|
+
"Content-Type: application/octet-stream\r\n"
|
326
|
+
params["files"][:name].should.equal "files"
|
327
|
+
params["files"][:tempfile].read.should.equal "contents"
|
328
|
+
end
|
329
|
+
|
330
|
+
should "parse filename with escaped quotes and modification param" do
|
331
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param))
|
332
|
+
params = Rack::Multipart.parse_multipart(env)
|
333
|
+
params["files"][:type].should.equal "image/jpeg"
|
334
|
+
params["files"][:filename].should.equal "\"human\" genome.jpeg"
|
335
|
+
params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
|
336
|
+
"Content-Disposition: attachment; " +
|
337
|
+
"name=\"files\"; " +
|
338
|
+
"filename=\"\"human\" genome.jpeg\"; " +
|
339
|
+
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
|
340
|
+
"Content-Description: a complete map of the human genome\r\n"
|
341
|
+
params["files"][:name].should.equal "files"
|
342
|
+
params["files"][:tempfile].read.should.equal "contents"
|
343
|
+
end
|
344
|
+
|
345
|
+
should "parse filename with unescaped percentage characters" do
|
346
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages, "----WebKitFormBoundary2NHc7OhsgU68l3Al"))
|
347
|
+
params = Rack::Multipart.parse_multipart(env)
|
348
|
+
files = params["document"]["attachment"]
|
349
|
+
files[:type].should.equal "image/jpeg"
|
350
|
+
files[:filename].should.equal "100% of a photo.jpeg"
|
351
|
+
files[:head].should.equal <<-MULTIPART
|
352
|
+
Content-Disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg"\r
|
353
|
+
Content-Type: image/jpeg\r
|
354
|
+
MULTIPART
|
355
|
+
|
356
|
+
files[:name].should.equal "document[attachment]"
|
357
|
+
files[:tempfile].read.should.equal "contents"
|
358
|
+
end
|
359
|
+
|
360
|
+
should "parse filename with unescaped percentage characters that look like partial hex escapes" do
|
361
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages2, "----WebKitFormBoundary2NHc7OhsgU68l3Al"))
|
362
|
+
params = Rack::Multipart.parse_multipart(env)
|
363
|
+
files = params["document"]["attachment"]
|
364
|
+
files[:type].should.equal "image/jpeg"
|
365
|
+
files[:filename].should.equal "100%a"
|
366
|
+
files[:head].should.equal <<-MULTIPART
|
367
|
+
Content-Disposition: form-data; name="document[attachment]"; filename="100%a"\r
|
368
|
+
Content-Type: image/jpeg\r
|
369
|
+
MULTIPART
|
370
|
+
|
371
|
+
files[:name].should.equal "document[attachment]"
|
372
|
+
files[:tempfile].read.should.equal "contents"
|
373
|
+
end
|
374
|
+
|
375
|
+
should "parse filename with unescaped percentage characters that look like partial hex escapes" do
|
376
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages3, "----WebKitFormBoundary2NHc7OhsgU68l3Al"))
|
377
|
+
params = Rack::Multipart.parse_multipart(env)
|
378
|
+
files = params["document"]["attachment"]
|
379
|
+
files[:type].should.equal "image/jpeg"
|
380
|
+
files[:filename].should.equal "100%"
|
381
|
+
files[:head].should.equal <<-MULTIPART
|
382
|
+
Content-Disposition: form-data; name="document[attachment]"; filename="100%"\r
|
383
|
+
Content-Type: image/jpeg\r
|
384
|
+
MULTIPART
|
385
|
+
|
386
|
+
files[:name].should.equal "document[attachment]"
|
387
|
+
files[:tempfile].read.should.equal "contents"
|
388
|
+
end
|
389
|
+
|
390
|
+
it "rewinds input after parsing upload" do
|
391
|
+
options = multipart_fixture(:text)
|
392
|
+
input = options[:input]
|
393
|
+
env = Rack::MockRequest.env_for("/", options)
|
394
|
+
params = Rack::Multipart.parse_multipart(env)
|
395
|
+
params["submit-name"].should.equal "Larry"
|
396
|
+
params["files"][:filename].should.equal "file1.txt"
|
397
|
+
input.read.length.should.equal 307
|
398
|
+
end
|
399
|
+
|
400
|
+
it "builds multipart body" do
|
401
|
+
files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
|
402
|
+
data = Rack::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
|
403
|
+
|
404
|
+
options = {
|
405
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
406
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
407
|
+
:input => StringIO.new(data)
|
408
|
+
}
|
409
|
+
env = Rack::MockRequest.env_for("/", options)
|
410
|
+
params = Rack::Multipart.parse_multipart(env)
|
411
|
+
params["submit-name"].should.equal "Larry"
|
412
|
+
params["files"][:filename].should.equal "file1.txt"
|
413
|
+
params["files"][:tempfile].read.should.equal "contents"
|
414
|
+
end
|
415
|
+
|
416
|
+
it "builds nested multipart body" do
|
417
|
+
files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
|
418
|
+
data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
|
419
|
+
|
420
|
+
options = {
|
421
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
422
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
423
|
+
:input => StringIO.new(data)
|
424
|
+
}
|
425
|
+
env = Rack::MockRequest.env_for("/", options)
|
426
|
+
params = Rack::Multipart.parse_multipart(env)
|
427
|
+
params["people"][0]["submit-name"].should.equal "Larry"
|
428
|
+
params["people"][0]["files"][:filename].should.equal "file1.txt"
|
429
|
+
params["people"][0]["files"][:tempfile].read.should.equal "contents"
|
430
|
+
end
|
431
|
+
|
432
|
+
it "can parse fields that end at the end of the buffer" do
|
433
|
+
input = File.read(multipart_file("bad_robots"))
|
434
|
+
|
435
|
+
req = Rack::Request.new Rack::MockRequest.env_for("/",
|
436
|
+
"CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
|
437
|
+
"CONTENT_LENGTH" => input.size,
|
438
|
+
:input => input)
|
439
|
+
|
440
|
+
req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
|
441
|
+
req.POST['addresses'].should.not.equal nil
|
442
|
+
end
|
443
|
+
|
444
|
+
it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
|
445
|
+
data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |f| f.read }.gsub(/\n/, "\r\n")
|
446
|
+
options = {
|
447
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
|
448
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
449
|
+
:input => StringIO.new(data)
|
450
|
+
}
|
451
|
+
env = Rack::MockRequest.env_for("/", options)
|
452
|
+
params = Rack::Multipart.parse_multipart(env)
|
453
|
+
|
454
|
+
params.should.not.equal nil
|
455
|
+
params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
|
456
|
+
params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
|
457
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
|
458
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
|
459
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
|
460
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
|
461
|
+
end
|
462
|
+
|
463
|
+
should "return nil if no UploadedFiles were used" do
|
464
|
+
data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
|
465
|
+
data.should.equal nil
|
466
|
+
end
|
467
|
+
|
468
|
+
should "raise ArgumentError if params is not a Hash" do
|
469
|
+
lambda { Rack::Multipart.build_multipart("foo=bar") }.
|
470
|
+
should.raise(ArgumentError).
|
471
|
+
message.should.equal "value must be a Hash"
|
472
|
+
end
|
473
|
+
|
474
|
+
it "can parse fields with a content type" do
|
475
|
+
data = <<-EOF
|
476
|
+
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon\r
|
477
|
+
Content-Disposition: form-data; name="description"\r
|
478
|
+
Content-Type: text/plain"\r
|
479
|
+
\r
|
480
|
+
Very very blue\r
|
481
|
+
--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--\r
|
482
|
+
EOF
|
483
|
+
options = {
|
484
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
|
485
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
486
|
+
:input => StringIO.new(data)
|
487
|
+
}
|
488
|
+
env = Rack::MockRequest.env_for("/", options)
|
489
|
+
params = Rack::Utils::Multipart.parse_multipart(env)
|
490
|
+
|
491
|
+
params.should.equal({"description"=>"Very very blue"})
|
492
|
+
end
|
493
|
+
|
494
|
+
should "parse multipart upload with no content-length header" do
|
495
|
+
env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit)
|
496
|
+
env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
|
497
|
+
env.delete 'CONTENT_LENGTH'
|
498
|
+
params = Rack::Multipart.parse_multipart(env)
|
499
|
+
params['profile']['bio'].should.include 'hello'
|
500
|
+
end
|
501
|
+
|
502
|
+
should "parse very long unquoted multipart file names" do
|
503
|
+
data = <<-EOF
|
504
|
+
--AaB03x\r
|
505
|
+
Content-Type: text/plain\r
|
506
|
+
Content-Disposition: attachment; name=file; filename=#{'long' * 100}\r
|
507
|
+
\r
|
508
|
+
contents\r
|
509
|
+
--AaB03x--\r
|
510
|
+
EOF
|
511
|
+
|
512
|
+
options = {
|
513
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
514
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
515
|
+
:input => StringIO.new(data)
|
516
|
+
}
|
517
|
+
env = Rack::MockRequest.env_for("/", options)
|
518
|
+
params = Rack::Utils::Multipart.parse_multipart(env)
|
519
|
+
|
520
|
+
params["file"][:filename].should.equal('long' * 100)
|
521
|
+
end
|
522
|
+
|
523
|
+
should "support mixed case metadata" do
|
524
|
+
file = multipart_file(:text)
|
525
|
+
data = File.open(file, 'rb') { |io| io.read }
|
526
|
+
|
527
|
+
type = "Multipart/Form-Data; Boundary=AaB03x"
|
528
|
+
length = data.respond_to?(:bytesize) ? data.bytesize : data.size
|
529
|
+
|
530
|
+
e = { "CONTENT_TYPE" => type,
|
531
|
+
"CONTENT_LENGTH" => length.to_s,
|
532
|
+
:input => StringIO.new(data) }
|
533
|
+
|
534
|
+
env = Rack::MockRequest.env_for("/", e)
|
535
|
+
params = Rack::Multipart.parse_multipart(env)
|
536
|
+
params["submit-name"].should.equal "Larry"
|
537
|
+
params["submit-name-with-content"].should.equal "Berry"
|
538
|
+
params["files"][:type].should.equal "text/plain"
|
539
|
+
params["files"][:filename].should.equal "file1.txt"
|
540
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
541
|
+
"name=\"files\"; filename=\"file1.txt\"\r\n" +
|
542
|
+
"Content-Type: text/plain\r\n"
|
543
|
+
params["files"][:name].should.equal "files"
|
544
|
+
params["files"][:tempfile].read.should.equal "contents"
|
545
|
+
end
|
546
|
+
|
547
|
+
end
|