test-types-multipart.js 29 KB

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