allow to limit file size through the max_file_size setting
[coquelicot.git] / spec / coquelicot / rack / multipart_parser_spec.rb
1 # Coquelicot: "one-click" file sharing with a focus on users' privacy.
2 # Copyright © 2012 potager.org <jardiniers@potager.org>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 require 'spec_helper'
18
19 module Coquelicot::Rack
20   describe MultipartParser do
21     let(:env) { { 'SERVER_NAME' => 'example.org',
22                   'SERVER_PORT' => 80,
23                   'REQUEST_METHOD' => 'POST',
24                   'PATH_INFO' => '/upload',
25                   'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
26                   'CONTENT_LENGTH' => "#{defined?(input) ? input.size : 0}",
27                   'rack.input' => StringIO.new(defined?(input) ? input : '')
28                 } }
29     describe '.parse' do
30       context 'when given a block taking one argument' do
31         it 'should run the block with a new parser as argument' do
32           MultipartParser.parse(env) do |p|
33             p.should be_a(MultipartParser)
34           end
35         end
36       end
37     end
38
39     describe '#start' do
40       context 'when given no block' do
41         it 'should raise an error' do
42           MultipartParser.parse(env) do |p|
43             expect { p.start }.to raise_exception(ArgumentError)
44           end
45         end
46       end
47       context 'when used once' do
48         it 'should call the block on start' do
49           mock = double
50           mock.should_receive(:act)
51           MultipartParser.parse(env) do |p|
52             p.start { mock.act }
53           end
54         end
55       end
56       context 'when used twice in a row' do
57         it 'should call both blocks on start' do
58           mock = double
59           mock.should_receive(:run).ordered
60           mock.should_receive(:walk).ordered
61           MultipartParser.parse(env) do |p|
62             p.start { mock.run }
63             p.start { mock.walk }
64           end
65         end
66       end
67       context 'when used twice with steps inbetween' do
68         it 'should call both blocks on start' do
69           mock = double
70           mock.should_receive(:run).ordered
71           mock.should_receive(:walk).ordered
72           MultipartParser.parse(env) do |p|
73             p.start { mock.run }
74             p.many_fields
75             p.start { mock.walk }
76           end
77         end
78       end
79     end
80
81     describe '#many_fields' do
82       let(:input) do <<-MULTIPART_DATA.gsub(/^ */, '').gsub(/\n/, "\r\n")
83           --AaB03x
84           Content-Disposition: form-data; name="one"
85
86           1
87           --AaB03x
88           Content-Disposition: form-data; name="two"
89
90           2
91           --AaB03x
92           Content-Disposition: form-data; name="three"
93
94           3
95           --AaB03x--
96         MULTIPART_DATA
97       end
98       context 'when used alone' do
99         it 'should call the given block only once' do
100           mock = double
101           mock.should_receive(:act).once
102           MultipartParser.parse(env) do |p|
103             p.many_fields do |params|
104               mock.act
105             end
106           end
107         end
108         it 'should call the given block for all fields' do
109           MultipartParser.parse(env) do |p|
110             p.many_fields do |params|
111               params.should == { 'one' => '1', 'two' => '2', 'three' => '3' }
112             end
113           end
114         end
115       end
116       context 'positioned after "field"' do
117         it 'should call the given block only once' do
118           mock = double
119           mock.should_receive(:act).once
120           MultipartParser.parse(env) do |p|
121             p.field :one
122             p.many_fields do |params|
123               mock.act
124             end
125           end
126         end
127         it 'should call the given block for the remaning fields' do
128           MultipartParser.parse(env) do |p|
129             p.field :one
130             p.many_fields do |params|
131               params.should == { 'two' => '2', 'three' => '3' }
132             end
133           end
134         end
135       end
136       context 'positioned before "field"' do
137         it 'should call the given block only once' do
138           mock = double
139           mock.should_receive(:act).once
140           MultipartParser.parse(env) do |p|
141             p.many_fields do |params|
142               mock.act
143             end
144             p.field :three
145           end
146         end
147         it 'should call the given block for the first two fields' do
148           MultipartParser.parse(env) do |p|
149             p.many_fields do |params|
150               params.should == { 'one' => '1', 'two' => '2' }
151             end
152             p.field :three
153           end
154         end
155       end
156       context 'before and after "field"' do
157         it 'should call each given block only once' do
158           mock = double
159           mock.should_receive(:run).ordered
160           mock.should_receive(:walk).ordered
161           MultipartParser.parse(env) do |p|
162             p.many_fields do |params|
163               mock.run
164             end
165             p.field :two
166             p.many_fields do |params|
167               mock.walk
168             end
169           end
170         end
171         it 'should call each given block for the first and last fields, respectively' do
172           MultipartParser.parse(env) do |p|
173             p.many_fields do |params|
174               params.should == { 'one' => '1' }
175             end
176             p.field :two
177             p.many_fields do |params|
178               params.should == { 'three' => '3' }
179             end
180           end
181         end
182       end
183     end
184     describe '#field' do
185       let(:input) do <<-MULTIPART_DATA.gsub(/^ */, '').gsub(/\n/, "\r\n")
186           --AaB03x
187           Content-Disposition: form-data; name="one"
188
189           1
190           --AaB03x
191           Content-Disposition: form-data; name="two"
192
193           2
194           --AaB03x
195           Content-Disposition: form-data; name="three"
196
197           3
198           --AaB03x--
199         MULTIPART_DATA
200       end
201       context 'when positioned like the request' do
202         it 'should call a block for each field' do
203           mock = double
204           mock.should_receive(:first).with('1').ordered
205           mock.should_receive(:second).with('2').ordered
206           mock.should_receive(:third).with('3').ordered
207           MultipartParser.parse(env) do |p|
208             p.field(:one)   { |value| mock.first  value }
209             p.field(:two)   { |value| mock.second value }
210             p.field(:three) { |value| mock.third  value }
211           end
212         end
213       end
214       context 'when request field does not match' do
215         it 'should issue an error' do
216           expect {
217             MultipartParser.parse(env) do |p|
218               p.field(:whatever)
219             end
220           }.to raise_exception(EOFError)
221         end
222       end
223       context 'when request field does not match after many_fields' do
224         it 'should not call the field block' do
225           mock = double
226           mock.should_not_receive(:foo)
227           MultipartParser.parse(env) do |p|
228             p.many_fields
229             p.field(:whatever) { mock.foo }
230           end
231         end
232       end
233       context 'when request field  match after many_fields' do
234         it 'should call the field block' do
235           mock = double
236           mock.should_receive(:foo).with('3')
237           MultipartParser.parse(env) do |p|
238             p.many_fields
239             p.field(:three) { |value| mock.foo(value) }
240           end
241         end
242       end
243     end
244     describe '#file' do
245       context 'when file is at the end of the request' do
246         let(:file) { __FILE__ }
247         let(:input) { Rack::Multipart::Generator.new(
248             'field1' => '1',
249             'field2' => '2',
250             'field3' => Rack::Multipart::UploadedFile.new(file)
251           ).dump }
252         context 'when positioned like the request' do
253           it 'should call the given block in the right order' do
254             mock = double
255             mock.should_receive(:first).ordered
256             mock.should_receive(:second).ordered
257             mock.should_receive(:third).ordered
258             MultipartParser.parse(env) do |p|
259               p.field(:field1) { |value| mock.first }
260               p.field(:field2) { |value| mock.second }
261               p.file(:field3) do |filename, content_type, reader|
262                 mock.third
263                 while reader.call; end # flush file data
264               end
265             end
266           end
267           it 'should call the block passing the filename' do
268             filename = File.basename(file)
269             MultipartParser.parse(env) do |p|
270               p.many_fields
271               p.file(:field3) do |filename, content_type, reader|
272                 filename.should == filename
273                 while reader.call; end # flush file data
274               end
275             end
276           end
277           it 'should call the block passing the content type' do
278             MultipartParser.parse(env) do |p|
279               p.many_fields
280               p.file(:field3) do |filename, content_type, reader|
281                 content_type.should == 'text/plain'
282                 while reader.call; end # flush file data
283               end
284             end
285           end
286           it 'should read the whole file with multiple reader.call' do
287             data = ''
288             MultipartParser.parse(env) do |p|
289               p.many_fields
290               p.file(:field3) do |filename, content_type, reader|
291                 buf = ''
292                 data << buf until (buf = reader.call).nil?
293               end
294             end
295             data.should == File.read(file)
296           end
297         end
298       end
299
300       context 'when file is at the middle of the request' do
301         let(:file) { __FILE__ }
302         let(:input) { Rack::Multipart::Generator.new(
303             'field1' => '1',
304             'field2' => Rack::Multipart::UploadedFile.new(file),
305             'field3' => '3'
306           ).dump }
307         context 'when positioned like the request' do
308           it 'should call the given block in the right order' do
309             mock = double
310             mock.should_receive(:first).ordered
311             mock.should_receive(:second).ordered
312             mock.should_receive(:third).ordered
313             MultipartParser.parse(env) do |p|
314               p.field(:field1) { |value| mock.first }
315               p.file(:field2) do |filename, content_type, reader|
316                 mock.second
317                 while reader.call; end # flush file data
318               end
319               p.field(:field3) { |value| mock.third }
320             end
321           end
322           it 'should read the whole file with multiple reader.call' do
323             data = ''
324             MultipartParser.parse(env) do |p|
325               p.field(:field1)
326               p.file(:field2) do |filename, content_type, reader|
327                 buf = ''
328                 data << buf until (buf = reader.call).nil?
329               end
330               p.field(:field3)
331             end
332             data.should == File.read(file)
333           end
334         end
335       end
336       context 'when there two files follow each others in the request' do
337         let(:file1) { __FILE__ }
338         let(:file2) { File.expand_path('../../../spec_helper.rb', __FILE__) }
339         let(:input) { Rack::Multipart::Generator.new(
340             'field1' => Rack::Multipart::UploadedFile.new(file1),
341             'field2' => Rack::Multipart::UploadedFile.new(file2)
342           ).dump }
343         context 'when positioned like the request' do
344           it 'should call the given block in the right order' do
345             mock = double
346             mock.should_receive(:first).ordered
347             mock.should_receive(:second).ordered
348             MultipartParser.parse(env) do |p|
349               p.file(:field1) do |filename, content_type, reader|
350                 mock.first
351                 while reader.call; end # flush file data
352               end
353               p.file(:field2) do |filename, content_type, reader|
354                 mock.second
355                 buf = ''
356                 while reader.call; end # flush file data
357               end
358             end
359           end
360           it 'should read the files correctly' do
361             filename1 = File.basename(file1)
362             filename2 = File.basename(file2)
363             data1 = ''
364             data2 = ''
365             MultipartParser.parse(env) do |p|
366               p.file(:field1) do |filename, content_type, reader|
367                 filename.should == filename1
368                 buf = ''
369                 data1 << buf until (buf = reader.call).nil?
370               end
371               p.file(:field2) do |filename, content_type, reader|
372                 filename.should == filename2
373                 buf = ''
374                 data2 << buf until (buf = reader.call).nil?
375               end
376             end
377             data1.should == File.read(file1)
378             data2.should == File.read(file2)
379           end
380         end
381       end
382     end
383
384     describe '#finish' do
385       context 'when given no block' do
386         it 'should raise an error' do
387           MultipartParser.parse(env) do |p|
388             expect { p.finish }.to raise_exception(ArgumentError)
389           end
390         end
391       end
392       context 'when used once' do
393         it 'should call the block on finish' do
394           mock = mock('Object')
395           mock.should_receive(:act)
396           MultipartParser.parse(env) do |p|
397             p.finish { mock.act }
398           end
399         end
400       end
401       context 'when used twice in a row' do
402         it 'should call both blocks on finish (in reverse order)' do
403           mock = double
404           mock.should_receive(:run).ordered
405           mock.should_receive(:walk).ordered
406           MultipartParser.parse(env) do |p|
407             p.finish { mock.walk }
408             p.finish { mock.run }
409           end
410         end
411       end
412       context 'when used twice with steps inbetween' do
413         it 'should call both blocks on finish (in reverse order)' do
414           mock = double
415           mock.should_receive(:run).ordered
416           mock.should_receive(:walk).ordered
417           MultipartParser.parse(env) do |p|
418             p.finish { mock.walk }
419             p.many_fields
420             p.finish { mock.run }
421           end
422         end
423       end
424     end
425   end
426 end