test-types-urlencoded.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. 'use strict';
  2. const assert = require('assert');
  3. const { transcode } = require('buffer');
  4. const { inspect } = require('util');
  5. const busboy = require('..');
  6. const active = new Map();
  7. const tests = [
  8. { source: ['foo'],
  9. expected: [
  10. ['foo',
  11. '',
  12. { nameTruncated: false,
  13. valueTruncated: false,
  14. encoding: 'utf-8',
  15. mimeType: 'text/plain' },
  16. ],
  17. ],
  18. what: 'Unassigned value'
  19. },
  20. { source: ['foo=bar'],
  21. expected: [
  22. ['foo',
  23. 'bar',
  24. { nameTruncated: false,
  25. valueTruncated: false,
  26. encoding: 'utf-8',
  27. mimeType: 'text/plain' },
  28. ],
  29. ],
  30. what: 'Assigned value'
  31. },
  32. { source: ['foo&bar=baz'],
  33. expected: [
  34. ['foo',
  35. '',
  36. { nameTruncated: false,
  37. valueTruncated: false,
  38. encoding: 'utf-8',
  39. mimeType: 'text/plain' },
  40. ],
  41. ['bar',
  42. 'baz',
  43. { nameTruncated: false,
  44. valueTruncated: false,
  45. encoding: 'utf-8',
  46. mimeType: 'text/plain' },
  47. ],
  48. ],
  49. what: 'Unassigned and assigned value'
  50. },
  51. { source: ['foo=bar&baz'],
  52. expected: [
  53. ['foo',
  54. 'bar',
  55. { nameTruncated: false,
  56. valueTruncated: false,
  57. encoding: 'utf-8',
  58. mimeType: 'text/plain' },
  59. ],
  60. ['baz',
  61. '',
  62. { nameTruncated: false,
  63. valueTruncated: false,
  64. encoding: 'utf-8',
  65. mimeType: 'text/plain' },
  66. ],
  67. ],
  68. what: 'Assigned and unassigned value'
  69. },
  70. { source: ['foo=bar&baz=bla'],
  71. expected: [
  72. ['foo',
  73. 'bar',
  74. { nameTruncated: false,
  75. valueTruncated: false,
  76. encoding: 'utf-8',
  77. mimeType: 'text/plain' },
  78. ],
  79. ['baz',
  80. 'bla',
  81. { nameTruncated: false,
  82. valueTruncated: false,
  83. encoding: 'utf-8',
  84. mimeType: 'text/plain' },
  85. ],
  86. ],
  87. what: 'Two assigned values'
  88. },
  89. { source: ['foo&bar'],
  90. expected: [
  91. ['foo',
  92. '',
  93. { nameTruncated: false,
  94. valueTruncated: false,
  95. encoding: 'utf-8',
  96. mimeType: 'text/plain' },
  97. ],
  98. ['bar',
  99. '',
  100. { nameTruncated: false,
  101. valueTruncated: false,
  102. encoding: 'utf-8',
  103. mimeType: 'text/plain' },
  104. ],
  105. ],
  106. what: 'Two unassigned values'
  107. },
  108. { source: ['foo&bar&'],
  109. expected: [
  110. ['foo',
  111. '',
  112. { nameTruncated: false,
  113. valueTruncated: false,
  114. encoding: 'utf-8',
  115. mimeType: 'text/plain' },
  116. ],
  117. ['bar',
  118. '',
  119. { nameTruncated: false,
  120. valueTruncated: false,
  121. encoding: 'utf-8',
  122. mimeType: 'text/plain' },
  123. ],
  124. ],
  125. what: 'Two unassigned values and ampersand'
  126. },
  127. { source: ['foo+1=bar+baz%2Bquux'],
  128. expected: [
  129. ['foo 1',
  130. 'bar baz+quux',
  131. { nameTruncated: false,
  132. valueTruncated: false,
  133. encoding: 'utf-8',
  134. mimeType: 'text/plain' },
  135. ],
  136. ],
  137. what: 'Assigned key and value with (plus) space'
  138. },
  139. { source: ['foo=bar%20baz%21'],
  140. expected: [
  141. ['foo',
  142. 'bar baz!',
  143. { nameTruncated: false,
  144. valueTruncated: false,
  145. encoding: 'utf-8',
  146. mimeType: 'text/plain' },
  147. ],
  148. ],
  149. what: 'Assigned value with encoded bytes'
  150. },
  151. { source: ['foo%20bar=baz%20bla%21'],
  152. expected: [
  153. ['foo bar',
  154. 'baz bla!',
  155. { nameTruncated: false,
  156. valueTruncated: false,
  157. encoding: 'utf-8',
  158. mimeType: 'text/plain' },
  159. ],
  160. ],
  161. what: 'Assigned value with encoded bytes #2'
  162. },
  163. { source: ['foo=bar%20baz%21&num=1000'],
  164. expected: [
  165. ['foo',
  166. 'bar baz!',
  167. { nameTruncated: false,
  168. valueTruncated: false,
  169. encoding: 'utf-8',
  170. mimeType: 'text/plain' },
  171. ],
  172. ['num',
  173. '1000',
  174. { nameTruncated: false,
  175. valueTruncated: false,
  176. encoding: 'utf-8',
  177. mimeType: 'text/plain' },
  178. ],
  179. ],
  180. what: 'Two assigned values, one with encoded bytes'
  181. },
  182. { source: [
  183. Array.from(transcode(Buffer.from('foo'), 'utf8', 'utf16le')).map(
  184. (n) => `%${n.toString(16).padStart(2, '0')}`
  185. ).join(''),
  186. '=',
  187. Array.from(transcode(Buffer.from('😀!'), 'utf8', 'utf16le')).map(
  188. (n) => `%${n.toString(16).padStart(2, '0')}`
  189. ).join(''),
  190. ],
  191. expected: [
  192. ['foo',
  193. '😀!',
  194. { nameTruncated: false,
  195. valueTruncated: false,
  196. encoding: 'UTF-16LE',
  197. mimeType: 'text/plain' },
  198. ],
  199. ],
  200. charset: 'UTF-16LE',
  201. what: 'Encoded value with multi-byte charset'
  202. },
  203. { source: [
  204. 'foo=<',
  205. Array.from(transcode(Buffer.from('©:^þ'), 'utf8', 'latin1')).map(
  206. (n) => `%${n.toString(16).padStart(2, '0')}`
  207. ).join(''),
  208. ],
  209. expected: [
  210. ['foo',
  211. '<©:^þ',
  212. { nameTruncated: false,
  213. valueTruncated: false,
  214. encoding: 'ISO-8859-1',
  215. mimeType: 'text/plain' },
  216. ],
  217. ],
  218. charset: 'ISO-8859-1',
  219. what: 'Encoded value with single-byte, ASCII-compatible, non-UTF8 charset'
  220. },
  221. { source: ['foo=bar&baz=bla'],
  222. expected: [],
  223. what: 'Limits: zero fields',
  224. limits: { fields: 0 }
  225. },
  226. { source: ['foo=bar&baz=bla'],
  227. expected: [
  228. ['foo',
  229. 'bar',
  230. { nameTruncated: false,
  231. valueTruncated: false,
  232. encoding: 'utf-8',
  233. mimeType: 'text/plain' },
  234. ],
  235. ],
  236. what: 'Limits: one field',
  237. limits: { fields: 1 }
  238. },
  239. { source: ['foo=bar&baz=bla'],
  240. expected: [
  241. ['foo',
  242. 'bar',
  243. { nameTruncated: false,
  244. valueTruncated: false,
  245. encoding: 'utf-8',
  246. mimeType: 'text/plain' },
  247. ],
  248. ['baz',
  249. 'bla',
  250. { nameTruncated: false,
  251. valueTruncated: false,
  252. encoding: 'utf-8',
  253. mimeType: 'text/plain' },
  254. ],
  255. ],
  256. what: 'Limits: field part lengths match limits',
  257. limits: { fieldNameSize: 3, fieldSize: 3 }
  258. },
  259. { source: ['foo=bar&baz=bla'],
  260. expected: [
  261. ['fo',
  262. 'bar',
  263. { nameTruncated: true,
  264. valueTruncated: false,
  265. encoding: 'utf-8',
  266. mimeType: 'text/plain' },
  267. ],
  268. ['ba',
  269. 'bla',
  270. { nameTruncated: true,
  271. valueTruncated: false,
  272. encoding: 'utf-8',
  273. mimeType: 'text/plain' },
  274. ],
  275. ],
  276. what: 'Limits: truncated field name',
  277. limits: { fieldNameSize: 2 }
  278. },
  279. { source: ['foo=bar&baz=bla'],
  280. expected: [
  281. ['foo',
  282. 'ba',
  283. { nameTruncated: false,
  284. valueTruncated: true,
  285. encoding: 'utf-8',
  286. mimeType: 'text/plain' },
  287. ],
  288. ['baz',
  289. 'bl',
  290. { nameTruncated: false,
  291. valueTruncated: true,
  292. encoding: 'utf-8',
  293. mimeType: 'text/plain' },
  294. ],
  295. ],
  296. what: 'Limits: truncated field value',
  297. limits: { fieldSize: 2 }
  298. },
  299. { source: ['foo=bar&baz=bla'],
  300. expected: [
  301. ['fo',
  302. 'ba',
  303. { nameTruncated: true,
  304. valueTruncated: true,
  305. encoding: 'utf-8',
  306. mimeType: 'text/plain' },
  307. ],
  308. ['ba',
  309. 'bl',
  310. { nameTruncated: true,
  311. valueTruncated: true,
  312. encoding: 'utf-8',
  313. mimeType: 'text/plain' },
  314. ],
  315. ],
  316. what: 'Limits: truncated field name and value',
  317. limits: { fieldNameSize: 2, fieldSize: 2 }
  318. },
  319. { source: ['foo=bar&baz=bla'],
  320. expected: [
  321. ['fo',
  322. '',
  323. { nameTruncated: true,
  324. valueTruncated: true,
  325. encoding: 'utf-8',
  326. mimeType: 'text/plain' },
  327. ],
  328. ['ba',
  329. '',
  330. { nameTruncated: true,
  331. valueTruncated: true,
  332. encoding: 'utf-8',
  333. mimeType: 'text/plain' },
  334. ],
  335. ],
  336. what: 'Limits: truncated field name and zero value limit',
  337. limits: { fieldNameSize: 2, fieldSize: 0 }
  338. },
  339. { source: ['foo=bar&baz=bla'],
  340. expected: [
  341. ['',
  342. '',
  343. { nameTruncated: true,
  344. valueTruncated: true,
  345. encoding: 'utf-8',
  346. mimeType: 'text/plain' },
  347. ],
  348. ['',
  349. '',
  350. { nameTruncated: true,
  351. valueTruncated: true,
  352. encoding: 'utf-8',
  353. mimeType: 'text/plain' },
  354. ],
  355. ],
  356. what: 'Limits: truncated zero field name and zero value limit',
  357. limits: { fieldNameSize: 0, fieldSize: 0 }
  358. },
  359. { source: ['&'],
  360. expected: [],
  361. what: 'Ampersand'
  362. },
  363. { source: ['&&&&&'],
  364. expected: [],
  365. what: 'Many ampersands'
  366. },
  367. { source: ['='],
  368. expected: [
  369. ['',
  370. '',
  371. { nameTruncated: false,
  372. valueTruncated: false,
  373. encoding: 'utf-8',
  374. mimeType: 'text/plain' },
  375. ],
  376. ],
  377. what: 'Assigned value, empty name and value'
  378. },
  379. { source: [''],
  380. expected: [],
  381. what: 'Nothing'
  382. },
  383. ];
  384. for (const test of tests) {
  385. active.set(test, 1);
  386. const { what } = test;
  387. const charset = test.charset || 'utf-8';
  388. const bb = busboy({
  389. limits: test.limits,
  390. headers: {
  391. 'content-type': `application/x-www-form-urlencoded; charset=${charset}`,
  392. },
  393. });
  394. const results = [];
  395. bb.on('field', (key, val, info) => {
  396. results.push([key, val, info]);
  397. });
  398. bb.on('file', () => {
  399. throw new Error(`[${what}] Unexpected file`);
  400. });
  401. bb.on('close', () => {
  402. active.delete(test);
  403. assert.deepStrictEqual(
  404. results,
  405. test.expected,
  406. `[${what}] Results mismatch.\n`
  407. + `Parsed: ${inspect(results)}\n`
  408. + `Expected: ${inspect(test.expected)}`
  409. );
  410. });
  411. for (const src of test.source) {
  412. const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
  413. bb.write(buf);
  414. }
  415. bb.end();
  416. }
  417. // Byte-by-byte versions
  418. for (let test of tests) {
  419. test = { ...test };
  420. test.what += ' (byte-by-byte)';
  421. active.set(test, 1);
  422. const { what } = test;
  423. const charset = test.charset || 'utf-8';
  424. const bb = busboy({
  425. limits: test.limits,
  426. headers: {
  427. 'content-type': `application/x-www-form-urlencoded; charset="${charset}"`,
  428. },
  429. });
  430. const results = [];
  431. bb.on('field', (key, val, info) => {
  432. results.push([key, val, info]);
  433. });
  434. bb.on('file', () => {
  435. throw new Error(`[${what}] Unexpected file`);
  436. });
  437. bb.on('close', () => {
  438. active.delete(test);
  439. assert.deepStrictEqual(
  440. results,
  441. test.expected,
  442. `[${what}] Results mismatch.\n`
  443. + `Parsed: ${inspect(results)}\n`
  444. + `Expected: ${inspect(test.expected)}`
  445. );
  446. });
  447. for (const src of test.source) {
  448. const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src);
  449. for (let i = 0; i < buf.length; ++i)
  450. bb.write(buf.slice(i, i + 1));
  451. }
  452. bb.end();
  453. }
  454. {
  455. let exception = false;
  456. process.once('uncaughtException', (ex) => {
  457. exception = true;
  458. throw ex;
  459. });
  460. process.on('exit', () => {
  461. if (exception || active.size === 0)
  462. return;
  463. process.exitCode = 1;
  464. console.error('==========================');
  465. console.error(`${active.size} test(s) did not finish:`);
  466. console.error('==========================');
  467. console.error(Array.from(active.keys()).map((v) => v.what).join('\n'));
  468. });
  469. }