UNPKG

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