1 # -*- coding: UTF-8 -*-
2 # Coquelicot: "one-click" file sharing with a focus on users' privacy.
3 # Copyright © 2012-2013 potager.org <jardiniers@potager.org>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 module Coquelicot::Rack
21 describe MultipartParser do
22 let(:env) { { 'SERVER_NAME' => 'example.org',
24 'REQUEST_METHOD' => 'POST',
25 'PATH_INFO' => '/upload',
26 'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
27 'CONTENT_LENGTH' => "#{defined?(input) ? input.size : 0}",
28 'rack.input' => StringIO.new(defined?(input) ? input : '')
31 context 'when given a block taking one argument' do
32 it 'should run the block with a new parser as argument' do
33 MultipartParser.parse(env) do |p|
34 expect(p).to be_a(MultipartParser)
41 context 'when given no block' do
42 it 'should raise an error' do
43 MultipartParser.parse(env) do |p|
44 expect { p.start }.to raise_exception(ArgumentError)
48 context 'when used once' do
49 it 'should call the block on start' do
51 expect(mock).to receive(:act)
52 MultipartParser.parse(env) do |p|
57 context 'when used twice in a row' do
58 it 'should call both blocks on start' do
60 expect(mock).to receive(:run).ordered
61 expect(mock).to receive(:walk).ordered
62 MultipartParser.parse(env) do |p|
68 context 'when used twice with steps inbetween' do
69 it 'should call both blocks on start' do
71 expect(mock).to receive(:run).ordered
72 expect(mock).to receive(:walk).ordered
73 MultipartParser.parse(env) do |p|
82 describe '#many_fields' do
83 let(:input) do <<-MULTIPART_DATA.gsub(/^ */, '').gsub(/\n/, "\r\n")
85 Content-Disposition: form-data; name="one"
89 Content-Disposition: form-data; name="two"
93 Content-Disposition: form-data; name="three"
99 context 'when used alone' do
100 it 'should call the given block only once' do
102 expect(mock).to receive(:act).once
103 MultipartParser.parse(env) do |p|
104 p.many_fields do |params|
109 it 'should call the given block for all fields' do
110 MultipartParser.parse(env) do |p|
111 p.many_fields do |params|
112 expect(params).to be == { 'one' => '1', 'two' => '2', 'three' => '3' }
117 context 'positioned after "field"' do
118 it 'should call the given block only once' do
120 expect(mock).to receive(:act).once
121 MultipartParser.parse(env) do |p|
123 p.many_fields do |params|
128 it 'should call the given block for the remaning fields' do
129 MultipartParser.parse(env) do |p|
131 p.many_fields do |params|
132 expect(params).to be == { 'two' => '2', 'three' => '3' }
137 context 'positioned before "field"' do
138 it 'should call the given block only once' do
140 expect(mock).to receive(:act).once
141 MultipartParser.parse(env) do |p|
142 p.many_fields do |params|
148 it 'should call the given block for the first two fields' do
149 MultipartParser.parse(env) do |p|
150 p.many_fields do |params|
151 expect(params).to be == { 'one' => '1', 'two' => '2' }
157 context 'before and after "field"' do
158 it 'should call each given block only once' do
160 expect(mock).to receive(:run).ordered
161 expect(mock).to receive(:walk).ordered
162 MultipartParser.parse(env) do |p|
163 p.many_fields do |params|
167 p.many_fields do |params|
172 it 'should call each given block for the first and last fields, respectively' do
173 MultipartParser.parse(env) do |p|
174 p.many_fields do |params|
175 expect(params).to be == { 'one' => '1' }
178 p.many_fields do |params|
179 expect(params).to be == { 'three' => '3' }
186 let(:input) do <<-MULTIPART_DATA.gsub(/^ */, '').gsub(/\n/, "\r\n")
188 Content-Disposition: form-data; name="one"
192 Content-Disposition: form-data; name="two"
196 Content-Disposition: form-data; name="three"
202 context 'when positioned like the request' do
203 it 'should call a block for each field' do
205 expect(mock).to receive(:first).with('1').ordered
206 expect(mock).to receive(:second).with('2').ordered
207 expect(mock).to receive(:third).with('3').ordered
208 MultipartParser.parse(env) do |p|
209 p.field(:one) { |value| mock.first value }
210 p.field(:two) { |value| mock.second value }
211 p.field(:three) { |value| mock.third value }
215 context 'when request field does not match' do
216 it 'should issue an error' do
218 MultipartParser.parse(env) do |p|
221 }.to raise_exception(EOFError)
224 context 'when request field does not match after many_fields' do
225 it 'should not call the field block' do
227 expect(mock).not_to receive(:foo)
228 MultipartParser.parse(env) do |p|
230 p.field(:whatever) { mock.foo }
234 context 'when request field match after many_fields' do
235 it 'should call the field block' do
237 expect(mock).to receive(:foo).with('3')
238 MultipartParser.parse(env) do |p|
240 p.field(:three) { |value| mock.foo(value) }
246 context 'when file is at the end of the request' do
247 let(:file) { __FILE__ }
248 let(:input) { Rack::Multipart::Generator.new(
251 'field3' => Rack::Multipart::UploadedFile.new(file)
253 context 'when positioned like the request' do
254 it 'should call the given block in the right order' do
256 expect(mock).to receive(:first).ordered
257 expect(mock).to receive(:second).ordered
258 expect(mock).to receive(:third).ordered
259 MultipartParser.parse(env) do |p|
260 p.field(:field1) { |value| mock.first }
261 p.field(:field2) { |value| mock.second }
262 p.file(:field3) do |filename, content_type, reader|
264 while reader.call; end # flush file data
268 it 'should call the block passing the filename' do
269 filename = File.basename(file)
270 MultipartParser.parse(env) do |p|
272 p.file(:field3) do |filename, content_type, reader|
273 expect(filename).to be == filename
274 while reader.call; end # flush file data
278 it 'should call the block passing the content type' do
279 MultipartParser.parse(env) do |p|
281 p.file(:field3) do |filename, content_type, reader|
282 expect(content_type).to be == 'text/plain'
283 while reader.call; end # flush file data
287 it 'should read the whole file with multiple reader.call' do
289 MultipartParser.parse(env) do |p|
291 p.file(:field3) do |filename, content_type, reader|
293 data << buf until (buf = reader.call).nil?
296 expect(data).to be == slurp(file)
301 context 'when file is at the middle of the request' do
302 let(:file) { __FILE__ }
303 let(:input) { Rack::Multipart::Generator.new(
305 'field2' => Rack::Multipart::UploadedFile.new(file),
308 context 'when positioned like the request' do
309 it 'should call the given block in the right order' do
311 expect(mock).to receive(:first).ordered
312 expect(mock).to receive(:second).ordered
313 expect(mock).to receive(:third).ordered
314 MultipartParser.parse(env) do |p|
315 p.field(:field1) { |value| mock.first }
316 p.file(:field2) do |filename, content_type, reader|
318 while reader.call; end # flush file data
320 p.field(:field3) { |value| mock.third }
323 it 'should read the whole file with multiple reader.call' do
325 MultipartParser.parse(env) do |p|
327 p.file(:field2) do |filename, content_type, reader|
329 data << buf until (buf = reader.call).nil?
333 expect(data).to be == slurp(file)
337 context 'when there two files follow each others in the request' do
338 let(:file1) { __FILE__ }
339 let(:file2) { File.expand_path('../../../spec_helper.rb', __FILE__) }
340 let(:input) { Rack::Multipart::Generator.new(
341 'field1' => Rack::Multipart::UploadedFile.new(file1),
342 'field2' => Rack::Multipart::UploadedFile.new(file2)
344 context 'when positioned like the request' do
345 it 'should call the given block in the right order' do
347 expect(mock).to receive(:first).ordered
348 expect(mock).to receive(:second).ordered
349 MultipartParser.parse(env) do |p|
350 p.file(:field1) do |filename, content_type, reader|
352 while reader.call; end # flush file data
354 p.file(:field2) do |filename, content_type, reader|
357 while reader.call; end # flush file data
361 it 'should read the files correctly' do
362 filename1 = File.basename(file1)
363 filename2 = File.basename(file2)
366 MultipartParser.parse(env) do |p|
367 p.file(:field1) do |filename, content_type, reader|
368 expect(filename).to be == filename1
370 data1 << buf until (buf = reader.call).nil?
372 p.file(:field2) do |filename, content_type, reader|
373 expect(filename).to be == filename2
375 data2 << buf until (buf = reader.call).nil?
378 expect(data1).to be == slurp(file1)
379 expect(data2).to be == slurp(file2)
385 describe '#finish' do
386 context 'when given no block' do
387 it 'should raise an error' do
388 MultipartParser.parse(env) do |p|
389 expect { p.finish }.to raise_exception(ArgumentError)
393 context 'when used once' do
394 it 'should call the block on finish' do
396 expect(mock).to receive(:act)
397 MultipartParser.parse(env) do |p|
398 p.finish { mock.act }
402 context 'when used twice in a row' do
403 it 'should call both blocks on finish (in reverse order)' do
405 expect(mock).to receive(:run).ordered
406 expect(mock).to receive(:walk).ordered
407 MultipartParser.parse(env) do |p|
408 p.finish { mock.walk }
409 p.finish { mock.run }
413 context 'when used twice with steps inbetween' do
414 it 'should call both blocks on finish (in reverse order)' do
416 expect(mock).to receive(:run).ordered
417 expect(mock).to receive(:walk).ordered
418 MultipartParser.parse(env) do |p|
419 p.finish { mock.walk }
421 p.finish { mock.run }