UNPKG

30 kBJavaScriptView Raw
1'use strict';
2
3const assert = require('assert');
4const { inspect } = require('util');
5
6const busboy = require('..');
7
8const active = new Map();
9
10const tests = [
11 { source: [
12 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
13 'Content-Disposition: form-data; name="file_name_0"',
14 '',
15 'super alpha file',
16 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
17 'Content-Disposition: form-data; name="file_name_1"',
18 '',
19 'super beta file',
20 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
21 'Content-Disposition: form-data; '
22 + 'name="upload_file_0"; filename="1k_a.dat"',
23 'Content-Type: application/octet-stream',
24 '',
25 'A'.repeat(1023),
26 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
27 'Content-Disposition: form-data; '
28 + 'name="upload_file_1"; filename="1k_b.dat"',
29 'Content-Type: application/octet-stream',
30 '',
31 'B'.repeat(1023),
32 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
33 ].join('\r\n')
34 ],
35 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
36 expected: [
37 { type: 'field',
38 name: 'file_name_0',
39 val: 'super alpha file',
40 info: {
41 nameTruncated: false,
42 valueTruncated: false,
43 encoding: '7bit',
44 mimeType: 'text/plain',
45 },
46 },
47 { type: 'field',
48 name: 'file_name_1',
49 val: 'super beta file',
50 info: {
51 nameTruncated: false,
52 valueTruncated: false,
53 encoding: '7bit',
54 mimeType: 'text/plain',
55 },
56 },
57 { type: 'file',
58 name: 'upload_file_0',
59 data: Buffer.from('A'.repeat(1023)),
60 info: {
61 filename: '1k_a.dat',
62 encoding: '7bit',
63 mimeType: 'application/octet-stream',
64 },
65 limited: false,
66 },
67 { type: 'file',
68 name: 'upload_file_1',
69 data: Buffer.from('B'.repeat(1023)),
70 info: {
71 filename: '1k_b.dat',
72 encoding: '7bit',
73 mimeType: 'application/octet-stream',
74 },
75 limited: false,
76 },
77 ],
78 what: 'Fields and files'
79 },
80 { source: [
81 ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
82 'Content-Disposition: form-data; name="cont"',
83 '',
84 'some random content',
85 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
86 'Content-Disposition: form-data; name="pass"',
87 '',
88 'some random pass',
89 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
90 'Content-Disposition: form-data; name=bit',
91 '',
92 '2',
93 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
94 ].join('\r\n')
95 ],
96 boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
97 expected: [
98 { type: 'field',
99 name: 'cont',
100 val: 'some random content',
101 info: {
102 nameTruncated: false,
103 valueTruncated: false,
104 encoding: '7bit',
105 mimeType: 'text/plain',
106 },
107 },
108 { type: 'field',
109 name: 'pass',
110 val: 'some random pass',
111 info: {
112 nameTruncated: false,
113 valueTruncated: false,
114 encoding: '7bit',
115 mimeType: 'text/plain',
116 },
117 },
118 { type: 'field',
119 name: 'bit',
120 val: '2',
121 info: {
122 nameTruncated: false,
123 valueTruncated: false,
124 encoding: '7bit',
125 mimeType: 'text/plain',
126 },
127 },
128 ],
129 what: 'Fields only'
130 },
131 { source: [
132 ''
133 ],
134 boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
135 expected: [
136 { error: 'Unexpected end of form' },
137 ],
138 what: 'No fields and no files'
139 },
140 { source: [
141 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
142 'Content-Disposition: form-data; name="file_name_0"',
143 '',
144 'super alpha file',
145 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
146 'Content-Disposition: form-data; '
147 + 'name="upload_file_0"; filename="1k_a.dat"',
148 'Content-Type: application/octet-stream',
149 '',
150 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
151 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
152 ].join('\r\n')
153 ],
154 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
155 limits: {
156 fileSize: 13,
157 fieldSize: 5
158 },
159 expected: [
160 { type: 'field',
161 name: 'file_name_0',
162 val: 'super',
163 info: {
164 nameTruncated: false,
165 valueTruncated: true,
166 encoding: '7bit',
167 mimeType: 'text/plain',
168 },
169 },
170 { type: 'file',
171 name: 'upload_file_0',
172 data: Buffer.from('ABCDEFGHIJKLM'),
173 info: {
174 filename: '1k_a.dat',
175 encoding: '7bit',
176 mimeType: 'application/octet-stream',
177 },
178 limited: true,
179 },
180 ],
181 what: 'Fields and files (limits)'
182 },
183 { source: [
184 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
185 'Content-Disposition: form-data; name="file_name_0"',
186 '',
187 'super alpha file',
188 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
189 'Content-Disposition: form-data; '
190 + 'name="upload_file_0"; filename="1k_a.dat"',
191 'Content-Type: application/octet-stream',
192 '',
193 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
194 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
195 ].join('\r\n')
196 ],
197 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
198 limits: {
199 files: 0
200 },
201 expected: [
202 { type: 'field',
203 name: 'file_name_0',
204 val: 'super alpha file',
205 info: {
206 nameTruncated: false,
207 valueTruncated: false,
208 encoding: '7bit',
209 mimeType: 'text/plain',
210 },
211 },
212 'filesLimit',
213 ],
214 what: 'Fields and files (limits: 0 files)'
215 },
216 { source: [
217 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
218 'Content-Disposition: form-data; name="file_name_0"',
219 '',
220 'super alpha file',
221 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
222 'Content-Disposition: form-data; name="file_name_1"',
223 '',
224 'super beta file',
225 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
226 'Content-Disposition: form-data; '
227 + 'name="upload_file_0"; filename="1k_a.dat"',
228 'Content-Type: application/octet-stream',
229 '',
230 'A'.repeat(1023),
231 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
232 'Content-Disposition: form-data; '
233 + 'name="upload_file_1"; filename="1k_b.dat"',
234 'Content-Type: application/octet-stream',
235 '',
236 'B'.repeat(1023),
237 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
238 ].join('\r\n')
239 ],
240 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
241 expected: [
242 { type: 'field',
243 name: 'file_name_0',
244 val: 'super alpha file',
245 info: {
246 nameTruncated: false,
247 valueTruncated: false,
248 encoding: '7bit',
249 mimeType: 'text/plain',
250 },
251 },
252 { type: 'field',
253 name: 'file_name_1',
254 val: 'super beta file',
255 info: {
256 nameTruncated: false,
257 valueTruncated: false,
258 encoding: '7bit',
259 mimeType: 'text/plain',
260 },
261 },
262 ],
263 events: ['field'],
264 what: 'Fields and (ignored) files'
265 },
266 { source: [
267 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
268 'Content-Disposition: form-data; '
269 + 'name="upload_file_0"; filename="/tmp/1k_a.dat"',
270 'Content-Type: application/octet-stream',
271 '',
272 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
273 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
274 'Content-Disposition: form-data; '
275 + 'name="upload_file_1"; filename="C:\\files\\1k_b.dat"',
276 'Content-Type: application/octet-stream',
277 '',
278 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
279 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
280 'Content-Disposition: form-data; '
281 + 'name="upload_file_2"; filename="relative/1k_c.dat"',
282 'Content-Type: application/octet-stream',
283 '',
284 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
285 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
286 ].join('\r\n')
287 ],
288 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
289 expected: [
290 { type: 'file',
291 name: 'upload_file_0',
292 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
293 info: {
294 filename: '1k_a.dat',
295 encoding: '7bit',
296 mimeType: 'application/octet-stream',
297 },
298 limited: false,
299 },
300 { type: 'file',
301 name: 'upload_file_1',
302 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
303 info: {
304 filename: '1k_b.dat',
305 encoding: '7bit',
306 mimeType: 'application/octet-stream',
307 },
308 limited: false,
309 },
310 { type: 'file',
311 name: 'upload_file_2',
312 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
313 info: {
314 filename: '1k_c.dat',
315 encoding: '7bit',
316 mimeType: 'application/octet-stream',
317 },
318 limited: false,
319 },
320 ],
321 what: 'Files with filenames containing paths'
322 },
323 { source: [
324 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
325 'Content-Disposition: form-data; '
326 + 'name="upload_file_0"; filename="/absolute/1k_a.dat"',
327 'Content-Type: application/octet-stream',
328 '',
329 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
330 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
331 'Content-Disposition: form-data; '
332 + 'name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"',
333 'Content-Type: application/octet-stream',
334 '',
335 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
336 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
337 'Content-Disposition: form-data; '
338 + 'name="upload_file_2"; filename="relative/1k_c.dat"',
339 'Content-Type: application/octet-stream',
340 '',
341 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
342 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
343 ].join('\r\n')
344 ],
345 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
346 preservePath: true,
347 expected: [
348 { type: 'file',
349 name: 'upload_file_0',
350 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
351 info: {
352 filename: '/absolute/1k_a.dat',
353 encoding: '7bit',
354 mimeType: 'application/octet-stream',
355 },
356 limited: false,
357 },
358 { type: 'file',
359 name: 'upload_file_1',
360 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
361 info: {
362 filename: 'C:\\absolute\\1k_b.dat',
363 encoding: '7bit',
364 mimeType: 'application/octet-stream',
365 },
366 limited: false,
367 },
368 { type: 'file',
369 name: 'upload_file_2',
370 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
371 info: {
372 filename: 'relative/1k_c.dat',
373 encoding: '7bit',
374 mimeType: 'application/octet-stream',
375 },
376 limited: false,
377 },
378 ],
379 what: 'Paths to be preserved through the preservePath option'
380 },
381 { source: [
382 ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
383 'Content-Disposition: form-data; name="cont"',
384 'Content-Type: ',
385 '',
386 'some random content',
387 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
388 'Content-Disposition: ',
389 '',
390 'some random pass',
391 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
392 ].join('\r\n')
393 ],
394 boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
395 expected: [
396 { type: 'field',
397 name: 'cont',
398 val: 'some random content',
399 info: {
400 nameTruncated: false,
401 valueTruncated: false,
402 encoding: '7bit',
403 mimeType: 'text/plain',
404 },
405 },
406 ],
407 what: 'Empty content-type and empty content-disposition'
408 },
409 { source: [
410 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
411 'Content-Disposition: form-data; '
412 + 'name="file"; filename*=utf-8\'\'n%C3%A4me.txt',
413 'Content-Type: application/octet-stream',
414 '',
415 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
416 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
417 ].join('\r\n')
418 ],
419 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
420 expected: [
421 { type: 'file',
422 name: 'file',
423 data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
424 info: {
425 filename: 'näme.txt',
426 encoding: '7bit',
427 mimeType: 'application/octet-stream',
428 },
429 limited: false,
430 },
431 ],
432 what: 'Unicode filenames'
433 },
434 { source: [
435 ['--asdasdasdasd\r\n',
436 'Content-Type: text/plain\r\n',
437 'Content-Disposition: form-data; name="foo"\r\n',
438 '\r\n',
439 'asd\r\n',
440 '--asdasdasdasd--'
441 ].join(':)')
442 ],
443 boundary: 'asdasdasdasd',
444 expected: [
445 { error: 'Malformed part header' },
446 { error: 'Unexpected end of form' },
447 ],
448 what: 'Stopped mid-header'
449 },
450 { source: [
451 ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
452 'Content-Disposition: form-data; name="cont"',
453 'Content-Type: application/json',
454 '',
455 '{}',
456 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
457 ].join('\r\n')
458 ],
459 boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
460 expected: [
461 { type: 'field',
462 name: 'cont',
463 val: '{}',
464 info: {
465 nameTruncated: false,
466 valueTruncated: false,
467 encoding: '7bit',
468 mimeType: 'application/json',
469 },
470 },
471 ],
472 what: 'content-type for fields'
473 },
474 { source: [
475 '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
476 ],
477 boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
478 expected: [],
479 what: 'empty form'
480 },
481 { source: [
482 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
483 'Content-Disposition: form-data; '
484 + 'name=upload_file_0; filename="1k_a.dat"',
485 'Content-Type: application/octet-stream',
486 'Content-Transfer-Encoding: binary',
487 '',
488 '',
489 ].join('\r\n')
490 ],
491 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
492 expected: [
493 { type: 'file',
494 name: 'upload_file_0',
495 data: Buffer.alloc(0),
496 info: {
497 filename: '1k_a.dat',
498 encoding: 'binary',
499 mimeType: 'application/octet-stream',
500 },
501 limited: false,
502 err: 'Unexpected end of form',
503 },
504 { error: 'Unexpected end of form' },
505 ],
506 what: 'Stopped mid-file #1'
507 },
508 { source: [
509 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
510 'Content-Disposition: form-data; '
511 + 'name=upload_file_0; filename="1k_a.dat"',
512 'Content-Type: application/octet-stream',
513 '',
514 'a',
515 ].join('\r\n')
516 ],
517 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
518 expected: [
519 { type: 'file',
520 name: 'upload_file_0',
521 data: Buffer.from('a'),
522 info: {
523 filename: '1k_a.dat',
524 encoding: '7bit',
525 mimeType: 'application/octet-stream',
526 },
527 limited: false,
528 err: 'Unexpected end of form',
529 },
530 { error: 'Unexpected end of form' },
531 ],
532 what: 'Stopped mid-file #2'
533 },
534 { source: [
535 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
536 'Content-Disposition: form-data; '
537 + 'name="upload_file_0"; filename="notes.txt"',
538 'Content-Type: text/plain; charset=utf8',
539 '',
540 'a',
541 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
542 ].join('\r\n')
543 ],
544 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
545 expected: [
546 { type: 'file',
547 name: 'upload_file_0',
548 data: Buffer.from('a'),
549 info: {
550 filename: 'notes.txt',
551 encoding: '7bit',
552 mimeType: 'text/plain',
553 },
554 limited: false,
555 },
556 ],
557 what: 'Text file with charset'
558 },
559 { source: [
560 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
561 'Content-Disposition: form-data; '
562 + 'name="upload_file_0"; filename="notes.txt"',
563 'Content-Type: ',
564 ' text/plain; charset=utf8',
565 '',
566 'a',
567 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
568 ].join('\r\n')
569 ],
570 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
571 expected: [
572 { type: 'file',
573 name: 'upload_file_0',
574 data: Buffer.from('a'),
575 info: {
576 filename: 'notes.txt',
577 encoding: '7bit',
578 mimeType: 'text/plain',
579 },
580 limited: false,
581 },
582 ],
583 what: 'Folded header value'
584 },
585 { source: [
586 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
587 'Content-Type: text/plain; charset=utf8',
588 '',
589 'a',
590 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
591 ].join('\r\n')
592 ],
593 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
594 expected: [],
595 what: 'No Content-Disposition'
596 },
597 { source: [
598 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
599 'Content-Disposition: form-data; name="file_name_0"',
600 '',
601 'a'.repeat(64 * 1024),
602 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
603 'Content-Disposition: form-data; '
604 + 'name="upload_file_0"; filename="notes.txt"',
605 'Content-Type: ',
606 ' text/plain; charset=utf8',
607 '',
608 'bc',
609 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
610 ].join('\r\n')
611 ],
612 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
613 limits: {
614 fieldSize: Infinity,
615 },
616 expected: [
617 { type: 'file',
618 name: 'upload_file_0',
619 data: Buffer.from('bc'),
620 info: {
621 filename: 'notes.txt',
622 encoding: '7bit',
623 mimeType: 'text/plain',
624 },
625 limited: false,
626 },
627 ],
628 events: [ 'file' ],
629 what: 'Skip field parts if no listener'
630 },
631 { source: [
632 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
633 'Content-Disposition: form-data; name="file_name_0"',
634 '',
635 'a',
636 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
637 'Content-Disposition: form-data; '
638 + 'name="upload_file_0"; filename="notes.txt"',
639 'Content-Type: ',
640 ' text/plain; charset=utf8',
641 '',
642 'bc',
643 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
644 ].join('\r\n')
645 ],
646 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
647 limits: {
648 parts: 1,
649 },
650 expected: [
651 { type: 'field',
652 name: 'file_name_0',
653 val: 'a',
654 info: {
655 nameTruncated: false,
656 valueTruncated: false,
657 encoding: '7bit',
658 mimeType: 'text/plain',
659 },
660 },
661 'partsLimit',
662 ],
663 what: 'Parts limit'
664 },
665 { source: [
666 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
667 'Content-Disposition: form-data; name="file_name_0"',
668 '',
669 'a',
670 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
671 'Content-Disposition: form-data; name="file_name_1"',
672 '',
673 'b',
674 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
675 ].join('\r\n')
676 ],
677 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
678 limits: {
679 fields: 1,
680 },
681 expected: [
682 { type: 'field',
683 name: 'file_name_0',
684 val: 'a',
685 info: {
686 nameTruncated: false,
687 valueTruncated: false,
688 encoding: '7bit',
689 mimeType: 'text/plain',
690 },
691 },
692 'fieldsLimit',
693 ],
694 what: 'Fields limit'
695 },
696 { source: [
697 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
698 'Content-Disposition: form-data; '
699 + 'name="upload_file_0"; filename="notes.txt"',
700 'Content-Type: text/plain; charset=utf8',
701 '',
702 'ab',
703 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
704 'Content-Disposition: form-data; '
705 + 'name="upload_file_1"; filename="notes2.txt"',
706 'Content-Type: text/plain; charset=utf8',
707 '',
708 'cd',
709 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
710 ].join('\r\n')
711 ],
712 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
713 limits: {
714 files: 1,
715 },
716 expected: [
717 { type: 'file',
718 name: 'upload_file_0',
719 data: Buffer.from('ab'),
720 info: {
721 filename: 'notes.txt',
722 encoding: '7bit',
723 mimeType: 'text/plain',
724 },
725 limited: false,
726 },
727 'filesLimit',
728 ],
729 what: 'Files limit'
730 },
731 { source: [
732 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
733 'Content-Disposition: form-data; '
734 + `name="upload_file_0"; filename="${'a'.repeat(64 * 1024)}.txt"`,
735 'Content-Type: text/plain; charset=utf8',
736 '',
737 'ab',
738 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
739 'Content-Disposition: form-data; '
740 + 'name="upload_file_1"; filename="notes2.txt"',
741 'Content-Type: text/plain; charset=utf8',
742 '',
743 'cd',
744 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
745 ].join('\r\n')
746 ],
747 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
748 expected: [
749 { error: 'Malformed part header' },
750 { type: 'file',
751 name: 'upload_file_1',
752 data: Buffer.from('cd'),
753 info: {
754 filename: 'notes2.txt',
755 encoding: '7bit',
756 mimeType: 'text/plain',
757 },
758 limited: false,
759 },
760 ],
761 what: 'Oversized part header'
762 },
763 { source: [
764 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
765 'Content-Disposition: form-data; '
766 + 'name="upload_file_0"; filename="notes.txt"',
767 'Content-Type: text/plain; charset=utf8',
768 '',
769 'a'.repeat(31) + '\r',
770 ].join('\r\n'),
771 'b'.repeat(40),
772 '\r\n-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
773 ],
774 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
775 fileHwm: 32,
776 expected: [
777 { type: 'file',
778 name: 'upload_file_0',
779 data: Buffer.from('a'.repeat(31) + '\r' + 'b'.repeat(40)),
780 info: {
781 filename: 'notes.txt',
782 encoding: '7bit',
783 mimeType: 'text/plain',
784 },
785 limited: false,
786 },
787 ],
788 what: 'Lookbehind data should not stall file streams'
789 },
790 { source: [
791 ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
792 'Content-Disposition: form-data; '
793 + `name="upload_file_0"; filename="${'a'.repeat(8 * 1024)}.txt"`,
794 'Content-Type: text/plain; charset=utf8',
795 '',
796 'ab',
797 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
798 'Content-Disposition: form-data; '
799 + `name="upload_file_1"; filename="${'b'.repeat(8 * 1024)}.txt"`,
800 'Content-Type: text/plain; charset=utf8',
801 '',
802 'cd',
803 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
804 'Content-Disposition: form-data; '
805 + `name="upload_file_2"; filename="${'c'.repeat(8 * 1024)}.txt"`,
806 'Content-Type: text/plain; charset=utf8',
807 '',
808 'ef',
809 '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--',
810 ].join('\r\n')
811 ],
812 boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
813 expected: [
814 { type: 'file',
815 name: 'upload_file_0',
816 data: Buffer.from('ab'),
817 info: {
818 filename: `${'a'.repeat(8 * 1024)}.txt`,
819 encoding: '7bit',
820 mimeType: 'text/plain',
821 },
822 limited: false,
823 },
824 { type: 'file',
825 name: 'upload_file_1',
826 data: Buffer.from('cd'),
827 info: {
828 filename: `${'b'.repeat(8 * 1024)}.txt`,
829 encoding: '7bit',
830 mimeType: 'text/plain',
831 },
832 limited: false,
833 },
834 { type: 'file',
835 name: 'upload_file_2',
836 data: Buffer.from('ef'),
837 info: {
838 filename: `${'c'.repeat(8 * 1024)}.txt`,
839 encoding: '7bit',
840 mimeType: 'text/plain',
841 },
842 limited: false,
843 },
844 ],
845 what: 'Header size limit should be per part'
846 },
847 { source: [
848 '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee\r\n',
849 'Content-Type: application/gzip\r\n'
850 + 'Content-Encoding: gzip\r\n'
851 + 'Content-Disposition: form-data; name=batch-1; filename=batch-1'
852 + '\r\n\r\n',
853 '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee--',
854 ],
855 boundary: 'd1bf46b3-aa33-4061-b28d-6c5ced8b08ee',
856 expected: [
857 { type: 'file',
858 name: 'batch-1',
859 data: Buffer.alloc(0),
860 info: {
861 filename: 'batch-1',
862 encoding: '7bit',
863 mimeType: 'application/gzip',
864 },
865 limited: false,
866 },
867 ],
868 what: 'Empty part'
869 },
870];
871
872for (const test of tests) {
873 active.set(test, 1);
874
875 const { what, boundary, events, limits, preservePath, fileHwm } = test;
876 const bb = busboy({
877 fileHwm,
878 limits,
879 preservePath,
880 headers: {
881 'content-type': `multipart/form-data; boundary=${boundary}`,
882 }
883 });
884 const results = [];
885
886 if (events === undefined || events.includes('field')) {
887 bb.on('field', (name, val, info) => {
888 results.push({ type: 'field', name, val, info });
889 });
890 }
891
892 if (events === undefined || events.includes('file')) {
893 bb.on('file', (name, stream, info) => {
894 const data = [];
895 let nb = 0;
896 const file = {
897 type: 'file',
898 name,
899 data: null,
900 info,
901 limited: false,
902 };
903 results.push(file);
904 stream.on('data', (d) => {
905 data.push(d);
906 nb += d.length;
907 }).on('limit', () => {
908 file.limited = true;
909 }).on('close', () => {
910 file.data = Buffer.concat(data, nb);
911 assert.strictEqual(stream.truncated, file.limited);
912 }).once('error', (err) => {
913 file.err = err.message;
914 });
915 });
916 }
917
918 bb.on('error', (err) => {
919 results.push({ error: err.message });
920 });
921
922 bb.on('partsLimit', () => {
923 results.push('partsLimit');
924 });
925
926 bb.on('filesLimit', () => {
927 results.push('filesLimit');
928 });
929
930 bb.on('fieldsLimit', () => {
931 results.push('fieldsLimit');
932 });
933
934 bb.on('close', () => {
935 active.delete(test);
936
937 assert.deepStrictEqual(
938 results,
939 test.expected,
940 `[${what}] Results mismatch.\n`
941 + `Parsed: ${inspect(results)}\n`
942 + `Expected: ${inspect(test.expected)}`
943 );
944 });
945
946 for (const src of test.source) {
947 const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
948 bb.write(buf);
949 }
950 bb.end();
951}
952
953// Byte-by-byte versions
954for (let test of tests) {
955 test = { ...test };
956 test.what += ' (byte-by-byte)';
957 active.set(test, 1);
958
959 const { what, boundary, events, limits, preservePath, fileHwm } = test;
960 const bb = busboy({
961 fileHwm,
962 limits,
963 preservePath,
964 headers: {
965 'content-type': `multipart/form-data; boundary=${boundary}`,
966 }
967 });
968 const results = [];
969
970 if (events === undefined || events.includes('field')) {
971 bb.on('field', (name, val, info) => {
972 results.push({ type: 'field', name, val, info });
973 });
974 }
975
976 if (events === undefined || events.includes('file')) {
977 bb.on('file', (name, stream, info) => {
978 const data = [];
979 let nb = 0;
980 const file = {
981 type: 'file',
982 name,
983 data: null,
984 info,
985 limited: false,
986 };
987 results.push(file);
988 stream.on('data', (d) => {
989 data.push(d);
990 nb += d.length;
991 }).on('limit', () => {
992 file.limited = true;
993 }).on('close', () => {
994 file.data = Buffer.concat(data, nb);
995 assert.strictEqual(stream.truncated, file.limited);
996 }).once('error', (err) => {
997 file.err = err.message;
998 });
999 });
1000 }
1001
1002 bb.on('error', (err) => {
1003 results.push({ error: err.message });
1004 });
1005
1006 bb.on('partsLimit', () => {
1007 results.push('partsLimit');
1008 });
1009
1010 bb.on('filesLimit', () => {
1011 results.push('filesLimit');
1012 });
1013
1014 bb.on('fieldsLimit', () => {
1015 results.push('fieldsLimit');
1016 });
1017
1018 bb.on('close', () => {
1019 active.delete(test);
1020
1021 assert.deepStrictEqual(
1022 results,
1023 test.expected,
1024 `[${what}] Results mismatch.\n`
1025 + `Parsed: ${inspect(results)}\n`
1026 + `Expected: ${inspect(test.expected)}`
1027 );
1028 });
1029
1030 for (const src of test.source) {
1031 const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
1032 for (let i = 0; i < buf.length; ++i)
1033 bb.write(buf.slice(i, i + 1));
1034 }
1035 bb.end();
1036}
1037
1038{
1039 let exception = false;
1040 process.once('uncaughtException', (ex) => {
1041 exception = true;
1042 throw ex;
1043 });
1044 process.on('exit', () => {
1045 if (exception || active.size === 0)
1046 return;
1047 process.exitCode = 1;
1048 console.error('==========================');
1049 console.error(`${active.size} test(s) did not finish:`);
1050 console.error('==========================');
1051 console.error(Array.from(active.keys()).map((v) => v.what).join('\n'));
1052 });
1053}