Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 372 lines (325 sloc) 15.233 kb
24d5125 @shwoodard refactor of multipart module
shwoodard authored
1 require 'rack/utils'
2 require 'rack/mock'
3
4 describe Rack::Multipart do
5 def multipart_fixture(name)
6 file = multipart_file(name)
7 data = File.open(file, 'rb') { |io| io.read }
8
9 type = "multipart/form-data; boundary=AaB03x"
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
cc08f6e @brendan Added a new multipart fixture and spec to demonstrate that current 1.…
brendan authored
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
e8fb504 @evanphx Limit the size of parameter keys
evanphx authored
33 should "raise RangeError if the key space is exhausted" do
34 env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
35
36 old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
37 begin
38 lambda { Rack::Multipart.parse_multipart(env) }.should.raise(RangeError)
39 ensure
40 Rack::Utils.key_space_limit = old
41 end
42 end
43
fc4583f @raggi Merge branch 'multipart' of https://github.com/shwoodard/rack into sh…
raggi authored
44 should "parse multipart form webkit style" do
45 env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit)
46 env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
47 params = Rack::Multipart.parse_multipart(env)
48 params['profile']['bio'].should.include 'hello'
49 end
50
548b9af multipart/parser: avoid unbounded #gets method
Eric Wong authored
51 should "reject insanely long boundaries" do
52 # using a pipe since a tempfile can use up too much space
53 rd, wr = IO.pipe
54
55 # we only call rewind once at start, so make sure it succeeds
56 # and doesn't hit ESPIPE
57 def rd.rewind; end
58 wr.sync = true
59
60 # mock out length to make this pipe look like a Tempfile
61 def rd.length
62 1024 * 1024 * 8
63 end
64
65 # write to a pipe in a background thread, this will write a lot
66 # unless Rack (properly) shuts down the read end
67 thr = Thread.new do
68 begin
69 wr.write("--AaB03x")
70
71 # make the initial boundary a few gigs long
72 longer = "0123456789" * 1024 * 1024
73 (1024 * 1024).times { wr.write(longer) }
74
75 wr.write("\r\n")
76 wr.write('Content-Disposition: form-data; name="a"; filename="a.txt"')
77 wr.write("\r\n")
78 wr.write("Content-Type: text/plain\r\n")
79 wr.write("\r\na")
80 wr.write("--AaB03x--\r\n")
81 wr.close
82 rescue => err # this is EPIPE if Rack shuts us down
83 err
84 end
85 end
86
87 fixture = {
88 "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
89 "CONTENT_LENGTH" => rd.length.to_s,
90 :input => rd,
91 }
92
93 env = Rack::MockRequest.env_for '/', fixture
94 lambda {
95 Rack::Multipart.parse_multipart(env)
96 }.should.raise(EOFError)
97 rd.close
98
99 err = thr.value
100 err.should.be.instance_of Errno::EPIPE
101 wr.close
102 end
103
24d5125 @shwoodard refactor of multipart module
shwoodard authored
104 should "parse multipart upload with text file" do
105 env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
106 params = Rack::Multipart.parse_multipart(env)
107 params["submit-name"].should.equal "Larry"
108 params["submit-name-with-content"].should.equal "Berry"
109 params["files"][:type].should.equal "text/plain"
110 params["files"][:filename].should.equal "file1.txt"
111 params["files"][:head].should.equal "Content-Disposition: form-data; " +
112 "name=\"files\"; filename=\"file1.txt\"\r\n" +
113 "Content-Type: text/plain\r\n"
114 params["files"][:name].should.equal "files"
115 params["files"][:tempfile].read.should.equal "contents"
116 end
117
118 should "parse multipart upload with nested parameters" do
119 env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
120 params = Rack::Multipart.parse_multipart(env)
121 params["foo"]["submit-name"].should.equal "Larry"
122 params["foo"]["files"][:type].should.equal "text/plain"
123 params["foo"]["files"][:filename].should.equal "file1.txt"
124 params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
125 "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
126 "Content-Type: text/plain\r\n"
127 params["foo"]["files"][:name].should.equal "foo[files]"
128 params["foo"]["files"][:tempfile].read.should.equal "contents"
129 end
130
131 should "parse multipart upload with binary file" do
132 env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
133 params = Rack::Multipart.parse_multipart(env)
134 params["submit-name"].should.equal "Larry"
135 params["files"][:type].should.equal "image/png"
136 params["files"][:filename].should.equal "rack-logo.png"
137 params["files"][:head].should.equal "Content-Disposition: form-data; " +
138 "name=\"files\"; filename=\"rack-logo.png\"\r\n" +
139 "Content-Type: image/png\r\n"
140 params["files"][:name].should.equal "files"
141 params["files"][:tempfile].read.length.should.equal 26473
142 end
143
144 should "parse multipart upload with empty file" do
145 env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
146 params = Rack::Multipart.parse_multipart(env)
147 params["submit-name"].should.equal "Larry"
148 params["files"][:type].should.equal "text/plain"
149 params["files"][:filename].should.equal "file1.txt"
150 params["files"][:head].should.equal "Content-Disposition: form-data; " +
151 "name=\"files\"; filename=\"file1.txt\"\r\n" +
152 "Content-Type: text/plain\r\n"
153 params["files"][:name].should.equal "files"
154 params["files"][:tempfile].read.should.equal ""
155 end
156
157 should "parse multipart upload with filename with semicolons" do
158 env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon))
159 params = Rack::Multipart.parse_multipart(env)
160 params["files"][:type].should.equal "text/plain"
161 params["files"][:filename].should.equal "fi;le1.txt"
162 params["files"][:head].should.equal "Content-Disposition: form-data; " +
163 "name=\"files\"; filename=\"fi;le1.txt\"\r\n" +
164 "Content-Type: text/plain\r\n"
165 params["files"][:name].should.equal "files"
166 params["files"][:tempfile].read.should.equal "contents"
167 end
168
169 should "not include file params if no file was selected" do
170 env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
171 params = Rack::Multipart.parse_multipart(env)
172 params["submit-name"].should.equal "Larry"
173 params["files"].should.equal nil
174 params.keys.should.not.include "files"
175 end
176
e8a155a @jeremy Test that parsing a multipart/mixed upload no longer blows up
jeremy authored
177 should "parse multipart/mixed" do
178 env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files))
179 params = Rack::Utils::Multipart.parse_multipart(env)
180 params["foo"].should.equal "bar"
181 params["files"].should.be.instance_of String
182 params["files"].size.should.equal 252
183 end
184
24d5125 @shwoodard refactor of multipart module
shwoodard authored
185 should "parse IE multipart upload and clean up filename" do
186 env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
187 params = Rack::Multipart.parse_multipart(env)
188 params["files"][:type].should.equal "text/plain"
189 params["files"][:filename].should.equal "file1.txt"
190 params["files"][:head].should.equal "Content-Disposition: form-data; " +
191 "name=\"files\"; " +
192 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
193 "\r\nContent-Type: text/plain\r\n"
194 params["files"][:name].should.equal "files"
195 params["files"][:tempfile].read.should.equal "contents"
196 end
197
198 should "parse filename and modification param" do
199 env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param))
200 params = Rack::Multipart.parse_multipart(env)
201 params["files"][:type].should.equal "image/jpeg"
202 params["files"][:filename].should.equal "genome.jpeg"
203 params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
204 "Content-Disposition: attachment; " +
205 "name=\"files\"; " +
206 "filename=genome.jpeg; " +
207 "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
208 "Content-Description: a complete map of the human genome\r\n"
209 params["files"][:name].should.equal "files"
210 params["files"][:tempfile].read.should.equal "contents"
211 end
212
213 should "parse filename with escaped quotes" do
214 env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes))
215 params = Rack::Multipart.parse_multipart(env)
216 params["files"][:type].should.equal "application/octet-stream"
217 params["files"][:filename].should.equal "escape \"quotes"
218 params["files"][:head].should.equal "Content-Disposition: form-data; " +
219 "name=\"files\"; " +
220 "filename=\"escape \\\"quotes\"\r\n" +
221 "Content-Type: application/octet-stream\r\n"
222 params["files"][:name].should.equal "files"
223 params["files"][:tempfile].read.should.equal "contents"
224 end
225
226 should "parse filename with percent escaped quotes" do
227 env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes))
228 params = Rack::Multipart.parse_multipart(env)
229 params["files"][:type].should.equal "application/octet-stream"
230 params["files"][:filename].should.equal "escape \"quotes"
231 params["files"][:head].should.equal "Content-Disposition: form-data; " +
232 "name=\"files\"; " +
233 "filename=\"escape %22quotes\"\r\n" +
234 "Content-Type: application/octet-stream\r\n"
235 params["files"][:name].should.equal "files"
236 params["files"][:tempfile].read.should.equal "contents"
237 end
238
239 should "parse filename with unescaped quotes" do
240 env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes))
241 params = Rack::Multipart.parse_multipart(env)
242 params["files"][:type].should.equal "application/octet-stream"
243 params["files"][:filename].should.equal "escape \"quotes"
244 params["files"][:head].should.equal "Content-Disposition: form-data; " +
245 "name=\"files\"; " +
246 "filename=\"escape \"quotes\"\r\n" +
247 "Content-Type: application/octet-stream\r\n"
248 params["files"][:name].should.equal "files"
249 params["files"][:tempfile].read.should.equal "contents"
250 end
251
252 should "parse filename with escaped quotes and modification param" do
253 env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param))
254 params = Rack::Multipart.parse_multipart(env)
255 params["files"][:type].should.equal "image/jpeg"
256 params["files"][:filename].should.equal "\"human\" genome.jpeg"
257 params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
258 "Content-Disposition: attachment; " +
259 "name=\"files\"; " +
260 "filename=\"\"human\" genome.jpeg\"; " +
261 "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
262 "Content-Description: a complete map of the human genome\r\n"
263 params["files"][:name].should.equal "files"
264 params["files"][:tempfile].read.should.equal "contents"
265 end
266
267 it "rewinds input after parsing upload" do
268 options = multipart_fixture(:text)
269 input = options[:input]
270 env = Rack::MockRequest.env_for("/", options)
271 params = Rack::Multipart.parse_multipart(env)
272 params["submit-name"].should.equal "Larry"
273 params["files"][:filename].should.equal "file1.txt"
274 input.read.length.should.equal 307
275 end
276
277 it "builds multipart body" do
278 files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
279 data = Rack::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
280
281 options = {
282 "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
283 "CONTENT_LENGTH" => data.length.to_s,
284 :input => StringIO.new(data)
285 }
286 env = Rack::MockRequest.env_for("/", options)
287 params = Rack::Multipart.parse_multipart(env)
288 params["submit-name"].should.equal "Larry"
289 params["files"][:filename].should.equal "file1.txt"
290 params["files"][:tempfile].read.should.equal "contents"
291 end
292
293 it "builds nested multipart body" do
294 files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
295 data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
296
297 options = {
298 "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
299 "CONTENT_LENGTH" => data.length.to_s,
300 :input => StringIO.new(data)
301 }
302 env = Rack::MockRequest.env_for("/", options)
303 params = Rack::Multipart.parse_multipart(env)
304 params["people"][0]["submit-name"].should.equal "Larry"
305 params["people"][0]["files"][:filename].should.equal "file1.txt"
306 params["people"][0]["files"][:tempfile].read.should.equal "contents"
307 end
308
309 it "can parse fields that end at the end of the buffer" do
310 input = File.read(multipart_file("bad_robots"))
311
312 req = Rack::Request.new Rack::MockRequest.env_for("/",
313 "CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
314 "CONTENT_LENGTH" => input.size,
315 :input => input)
316
317 req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414"
318 req.POST['addresses'].should.not.equal nil
319 end
320
321 it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
322 data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
323 options = {
324 "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
325 "CONTENT_LENGTH" => data.length.to_s,
326 :input => StringIO.new(data)
327 }
328 env = Rack::MockRequest.env_for("/", options)
329 params = Rack::Multipart.parse_multipart(env)
330
331 params.should.not.equal nil
332 params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
333 params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
334 params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
335 params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
336 params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
337 params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
338 end
339
340 should "return nil if no UploadedFiles were used" do
341 data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
342 data.should.equal nil
343 end
344
345 should "raise ArgumentError if params is not a Hash" do
346 lambda { Rack::Multipart.build_multipart("foo=bar") }.
347 should.raise(ArgumentError).
348 message.should.equal "value must be a Hash"
349 end
fc4583f @raggi Merge branch 'multipart' of https://github.com/shwoodard/rack into sh…
raggi authored
350
c9f65df @raggi Fix parsing performance for unquoted filenames
raggi authored
351 should "parse very long unquoted multipart file names" do
352 data = <<-EOF
353 --AaB03x\r
354 Content-Type: text/plain\r
355 Content-Disposition: attachment; name=file; filename=#{'long' * 100}\r
356 \r
357 contents\r
358 --AaB03x--\r
359 EOF
360
361 options = {
362 "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
363 "CONTENT_LENGTH" => data.length.to_s,
364 :input => StringIO.new(data)
365 }
366 env = Rack::MockRequest.env_for("/", options)
367 params = Rack::Utils::Multipart.parse_multipart(env)
368
369 params["file"][:filename].should.equal('long' * 100)
370 end
e8a155a @jeremy Test that parsing a multipart/mixed upload no longer blows up
jeremy authored
371 end
Something went wrong with that request. Please try again.