index.cjs 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var fs = require('node:fs');
  4. var crypto = require('node:crypto');
  5. var node_events = require('node:events');
  6. var cuid2 = require('@paralleldrive/cuid2');
  7. var dezalgo = require('dezalgo');
  8. var fsPromises = require('node:fs/promises');
  9. var os = require('node:os');
  10. var path = require('node:path');
  11. var node_string_decoder = require('node:string_decoder');
  12. var once = require('once');
  13. var node_stream = require('node:stream');
  14. /* eslint-disable no-underscore-dangle */
  15. class PersistentFile extends node_events.EventEmitter {
  16. constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm }) {
  17. super();
  18. this.lastModifiedDate = null;
  19. Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm });
  20. this.size = 0;
  21. this._writeStream = null;
  22. if (typeof this.hashAlgorithm === 'string') {
  23. this.hash = crypto.createHash(this.hashAlgorithm);
  24. } else {
  25. this.hash = null;
  26. }
  27. }
  28. open() {
  29. this._writeStream = fs.createWriteStream(this.filepath);
  30. this._writeStream.on('error', (err) => {
  31. this.emit('error', err);
  32. });
  33. }
  34. toJSON() {
  35. const json = {
  36. size: this.size,
  37. filepath: this.filepath,
  38. newFilename: this.newFilename,
  39. mimetype: this.mimetype,
  40. mtime: this.lastModifiedDate,
  41. length: this.length,
  42. originalFilename: this.originalFilename,
  43. };
  44. if (this.hash && this.hash !== '') {
  45. json.hash = this.hash;
  46. }
  47. return json;
  48. }
  49. toString() {
  50. return `PersistentFile: ${this.newFilename}, Original: ${this.originalFilename}, Path: ${this.filepath}`;
  51. }
  52. write(buffer, cb) {
  53. if (this.hash) {
  54. this.hash.update(buffer);
  55. }
  56. if (this._writeStream.closed) {
  57. cb();
  58. return;
  59. }
  60. this._writeStream.write(buffer, () => {
  61. this.lastModifiedDate = new Date();
  62. this.size += buffer.length;
  63. this.emit('progress', this.size);
  64. cb();
  65. });
  66. }
  67. end(cb) {
  68. if (this.hash) {
  69. this.hash = this.hash.digest('hex');
  70. }
  71. this._writeStream.end(() => {
  72. this.emit('end');
  73. cb();
  74. });
  75. }
  76. destroy() {
  77. this._writeStream.destroy();
  78. const filepath = this.filepath;
  79. setTimeout(function () {
  80. fs.unlink(filepath, () => {});
  81. }, 1);
  82. }
  83. }
  84. /* eslint-disable no-underscore-dangle */
  85. class VolatileFile extends node_events.EventEmitter {
  86. constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }) {
  87. super();
  88. this.lastModifiedDate = null;
  89. Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream });
  90. this.size = 0;
  91. this._writeStream = null;
  92. if (typeof this.hashAlgorithm === 'string') {
  93. this.hash = crypto.createHash(this.hashAlgorithm);
  94. } else {
  95. this.hash = null;
  96. }
  97. }
  98. open() {
  99. this._writeStream = this.createFileWriteStream(this);
  100. this._writeStream.on('error', (err) => {
  101. this.emit('error', err);
  102. });
  103. }
  104. destroy() {
  105. this._writeStream.destroy();
  106. }
  107. toJSON() {
  108. const json = {
  109. size: this.size,
  110. newFilename: this.newFilename,
  111. length: this.length,
  112. originalFilename: this.originalFilename,
  113. mimetype: this.mimetype,
  114. };
  115. if (this.hash && this.hash !== '') {
  116. json.hash = this.hash;
  117. }
  118. return json;
  119. }
  120. toString() {
  121. return `VolatileFile: ${this.originalFilename}`;
  122. }
  123. write(buffer, cb) {
  124. if (this.hash) {
  125. this.hash.update(buffer);
  126. }
  127. if (this._writeStream.closed || this._writeStream.destroyed) {
  128. cb();
  129. return;
  130. }
  131. this._writeStream.write(buffer, () => {
  132. this.size += buffer.length;
  133. this.emit('progress', this.size);
  134. cb();
  135. });
  136. }
  137. end(cb) {
  138. if (this.hash) {
  139. this.hash = this.hash.digest('hex');
  140. }
  141. this._writeStream.end(() => {
  142. this.emit('end');
  143. cb();
  144. });
  145. }
  146. }
  147. const missingPlugin = 1000;
  148. const pluginFunction = 1001;
  149. const aborted = 1002;
  150. const noParser = 1003;
  151. const uninitializedParser = 1004;
  152. const filenameNotString = 1005;
  153. const maxFieldsSizeExceeded = 1006;
  154. const maxFieldsExceeded = 1007;
  155. const smallerThanMinFileSize = 1008;
  156. const biggerThanTotalMaxFileSize = 1009;
  157. const noEmptyFiles = 1010;
  158. const missingContentType = 1011;
  159. const malformedMultipart = 1012;
  160. const missingMultipartBoundary = 1013;
  161. const unknownTransferEncoding = 1014;
  162. const maxFilesExceeded = 1015;
  163. const biggerThanMaxFileSize = 1016;
  164. const pluginFailed = 1017;
  165. const cannotCreateDir = 1018;
  166. const FormidableError = class extends Error {
  167. constructor(message, internalCode, httpCode = 500) {
  168. super(message);
  169. this.code = internalCode;
  170. this.httpCode = httpCode;
  171. }
  172. };
  173. var FormidableError$1 = /*#__PURE__*/Object.freeze({
  174. __proto__: null,
  175. aborted: aborted,
  176. biggerThanMaxFileSize: biggerThanMaxFileSize,
  177. biggerThanTotalMaxFileSize: biggerThanTotalMaxFileSize,
  178. cannotCreateDir: cannotCreateDir,
  179. default: FormidableError,
  180. filenameNotString: filenameNotString,
  181. malformedMultipart: malformedMultipart,
  182. maxFieldsExceeded: maxFieldsExceeded,
  183. maxFieldsSizeExceeded: maxFieldsSizeExceeded,
  184. maxFilesExceeded: maxFilesExceeded,
  185. missingContentType: missingContentType,
  186. missingMultipartBoundary: missingMultipartBoundary,
  187. missingPlugin: missingPlugin,
  188. noEmptyFiles: noEmptyFiles,
  189. noParser: noParser,
  190. pluginFailed: pluginFailed,
  191. pluginFunction: pluginFunction,
  192. smallerThanMinFileSize: smallerThanMinFileSize,
  193. uninitializedParser: uninitializedParser,
  194. unknownTransferEncoding: unknownTransferEncoding
  195. });
  196. /* eslint-disable no-underscore-dangle */
  197. class DummyParser extends node_stream.Transform {
  198. constructor(incomingForm, options = {}) {
  199. super();
  200. this.globalOptions = { ...options };
  201. this.incomingForm = incomingForm;
  202. }
  203. _flush(callback) {
  204. this.incomingForm.ended = true;
  205. this.incomingForm._maybeEnd();
  206. callback();
  207. }
  208. }
  209. /* eslint-disable no-fallthrough */
  210. /* eslint-disable no-bitwise */
  211. /* eslint-disable no-plusplus */
  212. /* eslint-disable no-underscore-dangle */
  213. let s = 0;
  214. const STATE = {
  215. PARSER_UNINITIALIZED: s++,
  216. START: s++,
  217. START_BOUNDARY: s++,
  218. HEADER_FIELD_START: s++,
  219. HEADER_FIELD: s++,
  220. HEADER_VALUE_START: s++,
  221. HEADER_VALUE: s++,
  222. HEADER_VALUE_ALMOST_DONE: s++,
  223. HEADERS_ALMOST_DONE: s++,
  224. PART_DATA_START: s++,
  225. PART_DATA: s++,
  226. PART_END: s++,
  227. END: s++,
  228. };
  229. let f = 1;
  230. const FBOUNDARY = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) };
  231. const LF = 10;
  232. const CR = 13;
  233. const SPACE = 32;
  234. const HYPHEN = 45;
  235. const COLON = 58;
  236. const A = 97;
  237. const Z = 122;
  238. function lower(c) {
  239. return c | 0x20;
  240. }
  241. const STATES = {};
  242. Object.keys(STATE).forEach((stateName) => {
  243. STATES[stateName] = STATE[stateName];
  244. });
  245. class MultipartParser extends node_stream.Transform {
  246. constructor(options = {}) {
  247. super({ readableObjectMode: true });
  248. this.boundary = null;
  249. this.boundaryChars = null;
  250. this.lookbehind = null;
  251. this.bufferLength = 0;
  252. this.state = STATE.PARSER_UNINITIALIZED;
  253. this.globalOptions = { ...options };
  254. this.index = null;
  255. this.flags = 0;
  256. }
  257. _endUnexpected() {
  258. return new FormidableError(
  259. `MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
  260. malformedMultipart,
  261. 400,
  262. );
  263. }
  264. _flush(done) {
  265. if (
  266. (this.state === STATE.HEADER_FIELD_START && this.index === 0) ||
  267. (this.state === STATE.PART_DATA && this.index === this.boundary.length)
  268. ) {
  269. this._handleCallback('partEnd');
  270. this._handleCallback('end');
  271. done();
  272. } else if (this.state !== STATE.END) {
  273. done(this._endUnexpected());
  274. } else {
  275. done();
  276. }
  277. }
  278. initWithBoundary(str) {
  279. this.boundary = Buffer.from(`\r\n--${str}`);
  280. this.lookbehind = Buffer.alloc(this.boundary.length + 8);
  281. this.state = STATE.START;
  282. this.boundaryChars = {};
  283. for (let i = 0; i < this.boundary.length; i++) {
  284. this.boundaryChars[this.boundary[i]] = true;
  285. }
  286. }
  287. // eslint-disable-next-line max-params
  288. _handleCallback(name, buf, start, end) {
  289. if (start !== undefined && start === end) {
  290. return;
  291. }
  292. this.push({ name, buffer: buf, start, end });
  293. }
  294. // eslint-disable-next-line max-statements
  295. _transform(buffer, _, done) {
  296. let i = 0;
  297. let prevIndex = this.index;
  298. let { index, state, flags } = this;
  299. const { lookbehind, boundary, boundaryChars } = this;
  300. const boundaryLength = boundary.length;
  301. const boundaryEnd = boundaryLength - 1;
  302. this.bufferLength = buffer.length;
  303. let c = null;
  304. let cl = null;
  305. const setMark = (name, idx) => {
  306. this[`${name}Mark`] = typeof idx === 'number' ? idx : i;
  307. };
  308. const clearMarkSymbol = (name) => {
  309. delete this[`${name}Mark`];
  310. };
  311. const dataCallback = (name, shouldClear) => {
  312. const markSymbol = `${name}Mark`;
  313. if (!(markSymbol in this)) {
  314. return;
  315. }
  316. if (!shouldClear) {
  317. this._handleCallback(name, buffer, this[markSymbol], buffer.length);
  318. setMark(name, 0);
  319. } else {
  320. this._handleCallback(name, buffer, this[markSymbol], i);
  321. clearMarkSymbol(name);
  322. }
  323. };
  324. for (i = 0; i < this.bufferLength; i++) {
  325. c = buffer[i];
  326. switch (state) {
  327. case STATE.PARSER_UNINITIALIZED:
  328. done(this._endUnexpected());
  329. return;
  330. case STATE.START:
  331. index = 0;
  332. state = STATE.START_BOUNDARY;
  333. case STATE.START_BOUNDARY:
  334. if (index === boundary.length - 2) {
  335. if (c === HYPHEN) {
  336. flags |= FBOUNDARY.LAST_BOUNDARY;
  337. } else if (c !== CR) {
  338. done(this._endUnexpected());
  339. return;
  340. }
  341. index++;
  342. break;
  343. } else if (index - 1 === boundary.length - 2) {
  344. if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) {
  345. this._handleCallback('end');
  346. state = STATE.END;
  347. flags = 0;
  348. } else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) {
  349. index = 0;
  350. this._handleCallback('partBegin');
  351. state = STATE.HEADER_FIELD_START;
  352. } else {
  353. done(this._endUnexpected());
  354. return;
  355. }
  356. break;
  357. }
  358. if (c !== boundary[index + 2]) {
  359. index = -2;
  360. }
  361. if (c === boundary[index + 2]) {
  362. index++;
  363. }
  364. break;
  365. case STATE.HEADER_FIELD_START:
  366. state = STATE.HEADER_FIELD;
  367. setMark('headerField');
  368. index = 0;
  369. case STATE.HEADER_FIELD:
  370. if (c === CR) {
  371. clearMarkSymbol('headerField');
  372. state = STATE.HEADERS_ALMOST_DONE;
  373. break;
  374. }
  375. index++;
  376. if (c === HYPHEN) {
  377. break;
  378. }
  379. if (c === COLON) {
  380. if (index === 1) {
  381. // empty header field
  382. done(this._endUnexpected());
  383. return;
  384. }
  385. dataCallback('headerField', true);
  386. state = STATE.HEADER_VALUE_START;
  387. break;
  388. }
  389. cl = lower(c);
  390. if (cl < A || cl > Z) {
  391. done(this._endUnexpected());
  392. return;
  393. }
  394. break;
  395. case STATE.HEADER_VALUE_START:
  396. if (c === SPACE) {
  397. break;
  398. }
  399. setMark('headerValue');
  400. state = STATE.HEADER_VALUE;
  401. case STATE.HEADER_VALUE:
  402. if (c === CR) {
  403. dataCallback('headerValue', true);
  404. this._handleCallback('headerEnd');
  405. state = STATE.HEADER_VALUE_ALMOST_DONE;
  406. }
  407. break;
  408. case STATE.HEADER_VALUE_ALMOST_DONE:
  409. if (c !== LF) {
  410. done(this._endUnexpected());
  411. return;
  412. }
  413. state = STATE.HEADER_FIELD_START;
  414. break;
  415. case STATE.HEADERS_ALMOST_DONE:
  416. if (c !== LF) {
  417. done(this._endUnexpected());
  418. return;
  419. }
  420. this._handleCallback('headersEnd');
  421. state = STATE.PART_DATA_START;
  422. break;
  423. case STATE.PART_DATA_START:
  424. state = STATE.PART_DATA;
  425. setMark('partData');
  426. case STATE.PART_DATA:
  427. prevIndex = index;
  428. if (index === 0) {
  429. // boyer-moore derived algorithm to safely skip non-boundary data
  430. i += boundaryEnd;
  431. while (i < this.bufferLength && !(buffer[i] in boundaryChars)) {
  432. i += boundaryLength;
  433. }
  434. i -= boundaryEnd;
  435. c = buffer[i];
  436. }
  437. if (index < boundary.length) {
  438. if (boundary[index] === c) {
  439. if (index === 0) {
  440. dataCallback('partData', true);
  441. }
  442. index++;
  443. } else {
  444. index = 0;
  445. }
  446. } else if (index === boundary.length) {
  447. index++;
  448. if (c === CR) {
  449. // CR = part boundary
  450. flags |= FBOUNDARY.PART_BOUNDARY;
  451. } else if (c === HYPHEN) {
  452. // HYPHEN = end boundary
  453. flags |= FBOUNDARY.LAST_BOUNDARY;
  454. } else {
  455. index = 0;
  456. }
  457. } else if (index - 1 === boundary.length) {
  458. if (flags & FBOUNDARY.PART_BOUNDARY) {
  459. index = 0;
  460. if (c === LF) {
  461. // unset the PART_BOUNDARY flag
  462. flags &= ~FBOUNDARY.PART_BOUNDARY;
  463. this._handleCallback('partEnd');
  464. this._handleCallback('partBegin');
  465. state = STATE.HEADER_FIELD_START;
  466. break;
  467. }
  468. } else if (flags & FBOUNDARY.LAST_BOUNDARY) {
  469. if (c === HYPHEN) {
  470. this._handleCallback('partEnd');
  471. this._handleCallback('end');
  472. state = STATE.END;
  473. flags = 0;
  474. } else {
  475. index = 0;
  476. }
  477. } else {
  478. index = 0;
  479. }
  480. }
  481. if (index > 0) {
  482. // when matching a possible boundary, keep a lookbehind reference
  483. // in case it turns out to be a false lead
  484. lookbehind[index - 1] = c;
  485. } else if (prevIndex > 0) {
  486. // if our boundary turned out to be rubbish, the captured lookbehind
  487. // belongs to partData
  488. this._handleCallback('partData', lookbehind, 0, prevIndex);
  489. prevIndex = 0;
  490. setMark('partData');
  491. // reconsider the current character even so it interrupted the sequence
  492. // it could be the beginning of a new sequence
  493. i--;
  494. }
  495. break;
  496. case STATE.END:
  497. break;
  498. default:
  499. done(this._endUnexpected());
  500. return;
  501. }
  502. }
  503. dataCallback('headerField');
  504. dataCallback('headerValue');
  505. dataCallback('partData');
  506. this.index = index;
  507. this.state = state;
  508. this.flags = flags;
  509. done();
  510. return this.bufferLength;
  511. }
  512. explain() {
  513. return `state = ${MultipartParser.stateToString(this.state)}`;
  514. }
  515. }
  516. // eslint-disable-next-line consistent-return
  517. MultipartParser.stateToString = (stateNumber) => {
  518. // eslint-disable-next-line no-restricted-syntax, guard-for-in
  519. for (const stateName in STATE) {
  520. const number = STATE[stateName];
  521. if (number === stateNumber) return stateName;
  522. }
  523. };
  524. var MultipartParser$1 = Object.assign(MultipartParser, { STATES });
  525. class OctetStreamParser extends node_stream.PassThrough {
  526. constructor(options = {}) {
  527. super();
  528. this.globalOptions = { ...options };
  529. }
  530. }
  531. /* eslint-disable no-underscore-dangle */
  532. const octetStreamType = 'octet-stream';
  533. // the `options` is also available through the `options` / `formidable.options`
  534. async function plugin$3(formidable, options) {
  535. // the `this` context is always formidable, as the first argument of a plugin
  536. // but this allows us to customize/test each plugin
  537. /* istanbul ignore next */
  538. const self = this || formidable;
  539. if (/octet-stream/i.test(self.headers['content-type'])) {
  540. await init$2.call(self, self, options);
  541. }
  542. return self;
  543. }
  544. // Note that it's a good practice (but it's up to you) to use the `this.options` instead
  545. // of the passed `options` (second) param, because when you decide
  546. // to test the plugin you can pass custom `this` context to it (and so `this.options`)
  547. async function init$2(_self, _opts) {
  548. this.type = octetStreamType;
  549. const originalFilename = this.headers['x-file-name'];
  550. const mimetype = this.headers['content-type'];
  551. const thisPart = {
  552. originalFilename,
  553. mimetype,
  554. };
  555. const newFilename = this._getNewName(thisPart);
  556. const filepath = this._joinDirectoryName(newFilename);
  557. const file = await this._newFile({
  558. newFilename,
  559. filepath,
  560. originalFilename,
  561. mimetype,
  562. });
  563. this.emit('fileBegin', originalFilename, file);
  564. file.open();
  565. this.openedFiles.push(file);
  566. this._flushing += 1;
  567. this._parser = new OctetStreamParser(this.options);
  568. // Keep track of writes that haven't finished so we don't emit the file before it's done being written
  569. let outstandingWrites = 0;
  570. this._parser.on('data', (buffer) => {
  571. this.pause();
  572. outstandingWrites += 1;
  573. file.write(buffer, () => {
  574. outstandingWrites -= 1;
  575. this.resume();
  576. if (this.ended) {
  577. this._parser.emit('doneWritingFile');
  578. }
  579. });
  580. });
  581. this._parser.on('end', () => {
  582. this._flushing -= 1;
  583. this.ended = true;
  584. const done = () => {
  585. file.end(() => {
  586. this.emit('file', 'file', file);
  587. this._maybeEnd();
  588. });
  589. };
  590. if (outstandingWrites === 0) {
  591. done();
  592. } else {
  593. this._parser.once('doneWritingFile', done);
  594. }
  595. });
  596. return this;
  597. }
  598. /* eslint-disable no-underscore-dangle */
  599. // This is a buffering parser, have a look at StreamingQuerystring.js for a streaming parser
  600. class QuerystringParser extends node_stream.Transform {
  601. constructor(options = {}) {
  602. super({ readableObjectMode: true });
  603. this.globalOptions = { ...options };
  604. this.buffer = '';
  605. this.bufferLength = 0;
  606. }
  607. _transform(buffer, encoding, callback) {
  608. this.buffer += buffer.toString('ascii');
  609. this.bufferLength = this.buffer.length;
  610. callback();
  611. }
  612. _flush(callback) {
  613. const fields = new URLSearchParams(this.buffer);
  614. for (const [key, value] of fields) {
  615. this.push({
  616. key,
  617. value,
  618. });
  619. }
  620. this.buffer = '';
  621. callback();
  622. }
  623. }
  624. /* eslint-disable no-underscore-dangle */
  625. const querystringType = 'urlencoded';
  626. // the `options` is also available through the `this.options` / `formidable.options`
  627. function plugin$2(formidable, options) {
  628. // the `this` context is always formidable, as the first argument of a plugin
  629. // but this allows us to customize/test each plugin
  630. /* istanbul ignore next */
  631. const self = this || formidable;
  632. if (/urlencoded/i.test(self.headers['content-type'])) {
  633. init$1.call(self, self, options);
  634. }
  635. return self;
  636. }
  637. // Note that it's a good practice (but it's up to you) to use the `this.options` instead
  638. // of the passed `options` (second) param, because when you decide
  639. // to test the plugin you can pass custom `this` context to it (and so `this.options`)
  640. function init$1(_self, _opts) {
  641. this.type = querystringType;
  642. const parser = new QuerystringParser(this.options);
  643. parser.on('data', ({ key, value }) => {
  644. this.emit('field', key, value);
  645. });
  646. parser.once('end', () => {
  647. this.ended = true;
  648. this._maybeEnd();
  649. });
  650. this._parser = parser;
  651. return this;
  652. }
  653. /* eslint-disable no-underscore-dangle */
  654. const multipartType = 'multipart';
  655. // the `options` is also available through the `options` / `formidable.options`
  656. function plugin$1(formidable, options) {
  657. // the `this` context is always formidable, as the first argument of a plugin
  658. // but this allows us to customize/test each plugin
  659. /* istanbul ignore next */
  660. const self = this || formidable;
  661. // NOTE: we (currently) support both multipart/form-data and multipart/related
  662. const multipart = /multipart/i.test(self.headers['content-type']);
  663. if (multipart) {
  664. const m = self.headers['content-type'].match(
  665. /boundary=(?:"([^"]+)"|([^;]+))/i,
  666. );
  667. if (m) {
  668. const initMultipart = createInitMultipart(m[1] || m[2]);
  669. initMultipart.call(self, self, options); // lgtm [js/superfluous-trailing-arguments]
  670. } else {
  671. const err = new FormidableError(
  672. 'bad content-type header, no multipart boundary',
  673. missingMultipartBoundary,
  674. 400,
  675. );
  676. self._error(err);
  677. }
  678. }
  679. return self;
  680. }
  681. // Note that it's a good practice (but it's up to you) to use the `this.options` instead
  682. // of the passed `options` (second) param, because when you decide
  683. // to test the plugin you can pass custom `this` context to it (and so `this.options`)
  684. function createInitMultipart(boundary) {
  685. return function initMultipart() {
  686. this.type = multipartType;
  687. const parser = new MultipartParser$1(this.options);
  688. let headerField;
  689. let headerValue;
  690. let part;
  691. parser.initWithBoundary(boundary);
  692. // eslint-disable-next-line max-statements, consistent-return
  693. parser.on('data', async ({ name, buffer, start, end }) => {
  694. if (name === 'partBegin') {
  695. part = new node_stream.Stream();
  696. part.readable = true;
  697. part.headers = {};
  698. part.name = null;
  699. part.originalFilename = null;
  700. part.mimetype = null;
  701. part.transferEncoding = this.options.encoding;
  702. part.transferBuffer = '';
  703. headerField = '';
  704. headerValue = '';
  705. } else if (name === 'headerField') {
  706. headerField += buffer.toString(this.options.encoding, start, end);
  707. } else if (name === 'headerValue') {
  708. headerValue += buffer.toString(this.options.encoding, start, end);
  709. } else if (name === 'headerEnd') {
  710. headerField = headerField.toLowerCase();
  711. part.headers[headerField] = headerValue;
  712. // matches either a quoted-string or a token (RFC 2616 section 19.5.1)
  713. const m = headerValue.match(
  714. // eslint-disable-next-line no-useless-escape
  715. /\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i,
  716. );
  717. if (headerField === 'content-disposition') {
  718. if (m) {
  719. part.name = m[2] || m[3] || '';
  720. }
  721. part.originalFilename = this._getFileName(headerValue);
  722. } else if (headerField === 'content-type') {
  723. part.mimetype = headerValue;
  724. } else if (headerField === 'content-transfer-encoding') {
  725. part.transferEncoding = headerValue.toLowerCase();
  726. }
  727. headerField = '';
  728. headerValue = '';
  729. } else if (name === 'headersEnd') {
  730. switch (part.transferEncoding) {
  731. case 'binary':
  732. case '7bit':
  733. case '8bit':
  734. case 'utf-8': {
  735. const dataPropagation = (ctx) => {
  736. if (ctx.name === 'partData') {
  737. part.emit('data', ctx.buffer.slice(ctx.start, ctx.end));
  738. }
  739. };
  740. const dataStopPropagation = (ctx) => {
  741. if (ctx.name === 'partEnd') {
  742. part.emit('end');
  743. parser.off('data', dataPropagation);
  744. parser.off('data', dataStopPropagation);
  745. }
  746. };
  747. parser.on('data', dataPropagation);
  748. parser.on('data', dataStopPropagation);
  749. break;
  750. }
  751. case 'base64': {
  752. const dataPropagation = (ctx) => {
  753. if (ctx.name === 'partData') {
  754. part.transferBuffer += ctx.buffer
  755. .slice(ctx.start, ctx.end)
  756. .toString('ascii');
  757. /*
  758. four bytes (chars) in base64 converts to three bytes in binary
  759. encoding. So we should always work with a number of bytes that
  760. can be divided by 4, it will result in a number of bytes that
  761. can be divided vy 3.
  762. */
  763. const offset = parseInt(part.transferBuffer.length / 4, 10) * 4;
  764. part.emit(
  765. 'data',
  766. Buffer.from(
  767. part.transferBuffer.substring(0, offset),
  768. 'base64',
  769. ),
  770. );
  771. part.transferBuffer = part.transferBuffer.substring(offset);
  772. }
  773. };
  774. const dataStopPropagation = (ctx) => {
  775. if (ctx.name === 'partEnd') {
  776. part.emit('data', Buffer.from(part.transferBuffer, 'base64'));
  777. part.emit('end');
  778. parser.off('data', dataPropagation);
  779. parser.off('data', dataStopPropagation);
  780. }
  781. };
  782. parser.on('data', dataPropagation);
  783. parser.on('data', dataStopPropagation);
  784. break;
  785. }
  786. default:
  787. return this._error(
  788. new FormidableError(
  789. 'unknown transfer-encoding',
  790. unknownTransferEncoding,
  791. 501,
  792. ),
  793. );
  794. }
  795. this._parser.pause();
  796. await this.onPart(part);
  797. this._parser.resume();
  798. } else if (name === 'end') {
  799. this.ended = true;
  800. this._maybeEnd();
  801. }
  802. });
  803. this._parser = parser;
  804. };
  805. }
  806. /* eslint-disable no-underscore-dangle */
  807. class JSONParser extends node_stream.Transform {
  808. constructor(options = {}) {
  809. super({ readableObjectMode: true });
  810. this.chunks = [];
  811. this.globalOptions = { ...options };
  812. }
  813. _transform(chunk, encoding, callback) {
  814. this.chunks.push(String(chunk)); // todo consider using a string decoder
  815. callback();
  816. }
  817. _flush(callback) {
  818. try {
  819. const fields = JSON.parse(this.chunks.join(''));
  820. this.push(fields);
  821. } catch (e) {
  822. callback(e);
  823. return;
  824. }
  825. this.chunks = null;
  826. callback();
  827. }
  828. }
  829. /* eslint-disable no-underscore-dangle */
  830. const jsonType = 'json';
  831. // the `options` is also available through the `this.options` / `formidable.options`
  832. function plugin(formidable, options) {
  833. // the `this` context is always formidable, as the first argument of a plugin
  834. // but this allows us to customize/test each plugin
  835. /* istanbul ignore next */
  836. const self = this || formidable;
  837. if (/json/i.test(self.headers['content-type'])) {
  838. init.call(self, self, options);
  839. }
  840. return self;
  841. }
  842. // Note that it's a good practice (but it's up to you) to use the `this.options` instead
  843. // of the passed `options` (second) param, because when you decide
  844. // to test the plugin you can pass custom `this` context to it (and so `this.options`)
  845. function init(_self, _opts) {
  846. this.type = jsonType;
  847. const parser = new JSONParser(this.options);
  848. parser.on('data', (fields) => {
  849. this.fields = fields;
  850. });
  851. parser.once('end', () => {
  852. this.ended = true;
  853. this._maybeEnd();
  854. });
  855. this._parser = parser;
  856. }
  857. /* eslint-disable class-methods-use-this */
  858. /* eslint-disable no-underscore-dangle */
  859. const CUID2_FINGERPRINT = `${process.env.NODE_ENV}-${os.platform()}-${os.hostname()}`;
  860. const createId = cuid2.init({ length: 25, fingerprint: CUID2_FINGERPRINT.toLowerCase() });
  861. const DEFAULT_OPTIONS = {
  862. maxFields: 1000,
  863. maxFieldsSize: 20 * 1024 * 1024,
  864. maxFiles: Infinity,
  865. maxFileSize: 200 * 1024 * 1024,
  866. maxTotalFileSize: undefined,
  867. minFileSize: 1,
  868. allowEmptyFiles: false,
  869. createDirsFromUploads: false,
  870. keepExtensions: false,
  871. encoding: 'utf-8',
  872. hashAlgorithm: false,
  873. uploadDir: os.tmpdir(),
  874. enabledPlugins: [plugin$3, plugin$2, plugin$1, plugin],
  875. fileWriteStreamHandler: null,
  876. defaultInvalidName: 'invalid-name',
  877. filter(_part) {
  878. return true;
  879. },
  880. filename: undefined,
  881. };
  882. function hasOwnProp(obj, key) {
  883. return Object.prototype.hasOwnProperty.call(obj, key);
  884. }
  885. const decorateForceSequential = function (promiseCreator) {
  886. /* forces a function that returns a promise to be sequential
  887. useful for fs for example */
  888. let lastPromise = Promise.resolve();
  889. return async function (...x) {
  890. const promiseWeAreWaitingFor = lastPromise;
  891. let currentPromise;
  892. let callback;
  893. // we need to change lastPromise before await anything,
  894. // otherwise 2 calls might wait the same thing
  895. lastPromise = new Promise(function (resolve) {
  896. callback = resolve;
  897. });
  898. await promiseWeAreWaitingFor;
  899. currentPromise = promiseCreator(...x);
  900. currentPromise.then(callback).catch(callback);
  901. return currentPromise;
  902. };
  903. };
  904. const createNecessaryDirectoriesAsync = decorateForceSequential(function (filePath) {
  905. const directoryname = path.dirname(filePath);
  906. return fsPromises.mkdir(directoryname, { recursive: true });
  907. });
  908. const invalidExtensionChar = (c) => {
  909. const code = c.charCodeAt(0);
  910. return !(
  911. code === 46 || // .
  912. (code >= 48 && code <= 57) ||
  913. (code >= 65 && code <= 90) ||
  914. (code >= 97 && code <= 122)
  915. );
  916. };
  917. class IncomingForm extends node_events.EventEmitter {
  918. constructor(options = {}) {
  919. super();
  920. this.options = { ...DEFAULT_OPTIONS, ...options };
  921. if (!this.options.maxTotalFileSize) {
  922. this.options.maxTotalFileSize = this.options.maxFileSize;
  923. }
  924. const dir = path.resolve(
  925. this.options.uploadDir || this.options.uploaddir || os.tmpdir(),
  926. );
  927. this.uploaddir = dir;
  928. this.uploadDir = dir;
  929. // initialize with null
  930. [
  931. 'error',
  932. 'headers',
  933. 'type',
  934. 'bytesExpected',
  935. 'bytesReceived',
  936. '_parser',
  937. 'req',
  938. ].forEach((key) => {
  939. this[key] = null;
  940. });
  941. this._setUpRename();
  942. this._flushing = 0;
  943. this._fieldsSize = 0;
  944. this._totalFileSize = 0;
  945. this._plugins = [];
  946. this.openedFiles = [];
  947. this.options.enabledPlugins = []
  948. .concat(this.options.enabledPlugins)
  949. .filter(Boolean);
  950. if (this.options.enabledPlugins.length === 0) {
  951. throw new FormidableError(
  952. 'expect at least 1 enabled builtin plugin, see options.enabledPlugins',
  953. missingPlugin,
  954. );
  955. }
  956. this.options.enabledPlugins.forEach((plugin) => {
  957. this.use(plugin);
  958. });
  959. this._setUpMaxFields();
  960. this._setUpMaxFiles();
  961. this.ended = undefined;
  962. this.type = undefined;
  963. }
  964. use(plugin) {
  965. if (typeof plugin !== 'function') {
  966. throw new FormidableError(
  967. '.use: expect `plugin` to be a function',
  968. pluginFunction,
  969. );
  970. }
  971. this._plugins.push(plugin.bind(this));
  972. return this;
  973. }
  974. pause () {
  975. try {
  976. this.req.pause();
  977. } catch (err) {
  978. // the stream was destroyed
  979. if (!this.ended) {
  980. // before it was completed, crash & burn
  981. this._error(err);
  982. }
  983. return false;
  984. }
  985. return true;
  986. }
  987. resume () {
  988. try {
  989. this.req.resume();
  990. } catch (err) {
  991. // the stream was destroyed
  992. if (!this.ended) {
  993. // before it was completed, crash & burn
  994. this._error(err);
  995. }
  996. return false;
  997. }
  998. return true;
  999. }
  1000. // returns a promise if no callback is provided
  1001. async parse(req, cb) {
  1002. this.req = req;
  1003. let promise;
  1004. // Setup callback first, so we don't miss anything from data events emitted immediately.
  1005. if (!cb) {
  1006. let resolveRef;
  1007. let rejectRef;
  1008. promise = new Promise((resolve, reject) => {
  1009. resolveRef = resolve;
  1010. rejectRef = reject;
  1011. });
  1012. cb = (err, fields, files) => {
  1013. if (err) {
  1014. rejectRef(err);
  1015. } else {
  1016. resolveRef([fields, files]);
  1017. }
  1018. };
  1019. }
  1020. const callback = once(dezalgo(cb));
  1021. this.fields = {};
  1022. const files = {};
  1023. this.on('field', (name, value) => {
  1024. if (this.type === 'multipart' || this.type === 'urlencoded') {
  1025. if (!hasOwnProp(this.fields, name)) {
  1026. this.fields[name] = [value];
  1027. } else {
  1028. this.fields[name].push(value);
  1029. }
  1030. } else {
  1031. this.fields[name] = value;
  1032. }
  1033. });
  1034. this.on('file', (name, file) => {
  1035. if (!hasOwnProp(files, name)) {
  1036. files[name] = [file];
  1037. } else {
  1038. files[name].push(file);
  1039. }
  1040. });
  1041. this.on('error', (err) => {
  1042. callback(err, this.fields, files);
  1043. });
  1044. this.on('end', () => {
  1045. callback(null, this.fields, files);
  1046. });
  1047. // Parse headers and setup the parser, ready to start listening for data.
  1048. await this.writeHeaders(req.headers);
  1049. // Start listening for data.
  1050. req
  1051. .on('error', (err) => {
  1052. this._error(err);
  1053. })
  1054. .on('aborted', () => {
  1055. this.emit('aborted');
  1056. this._error(new FormidableError('Request aborted', aborted));
  1057. })
  1058. .on('data', (buffer) => {
  1059. try {
  1060. this.write(buffer);
  1061. } catch (err) {
  1062. this._error(err);
  1063. }
  1064. })
  1065. .on('end', () => {
  1066. if (this.error) {
  1067. return;
  1068. }
  1069. if (this._parser) {
  1070. this._parser.end();
  1071. }
  1072. });
  1073. if (promise) {
  1074. return promise;
  1075. }
  1076. return this;
  1077. }
  1078. async writeHeaders(headers) {
  1079. this.headers = headers;
  1080. this._parseContentLength();
  1081. await this._parseContentType();
  1082. if (!this._parser) {
  1083. this._error(
  1084. new FormidableError(
  1085. 'no parser found',
  1086. noParser,
  1087. 415, // Unsupported Media Type
  1088. ),
  1089. );
  1090. return;
  1091. }
  1092. this._parser.once('error', (error) => {
  1093. this._error(error);
  1094. });
  1095. }
  1096. write(buffer) {
  1097. if (this.error) {
  1098. return null;
  1099. }
  1100. if (!this._parser) {
  1101. this._error(
  1102. new FormidableError('uninitialized parser', uninitializedParser),
  1103. );
  1104. return null;
  1105. }
  1106. this.bytesReceived += buffer.length;
  1107. this.emit('progress', this.bytesReceived, this.bytesExpected);
  1108. this._parser.write(buffer);
  1109. return this.bytesReceived;
  1110. }
  1111. onPart(part) {
  1112. // this method can be overwritten by the user
  1113. return this._handlePart(part);
  1114. }
  1115. async _handlePart(part) {
  1116. if (part.originalFilename && typeof part.originalFilename !== 'string') {
  1117. this._error(
  1118. new FormidableError(
  1119. `the part.originalFilename should be string when it exists`,
  1120. filenameNotString,
  1121. ),
  1122. );
  1123. return;
  1124. }
  1125. // This MUST check exactly for undefined. You can not change it to !part.originalFilename.
  1126. // todo: uncomment when switch tests to Jest
  1127. // console.log(part);
  1128. // ? NOTE(@tunnckocore): no it can be any falsey value, it most probably depends on what's returned
  1129. // from somewhere else. Where recently I changed the return statements
  1130. // and such thing because code style
  1131. // ? NOTE(@tunnckocore): or even better, if there is no mimetype, then it's for sure a field
  1132. // ? NOTE(@tunnckocore): originalFilename is an empty string when a field?
  1133. if (!part.mimetype) {
  1134. let value = '';
  1135. const decoder = new node_string_decoder.StringDecoder(
  1136. part.transferEncoding || this.options.encoding,
  1137. );
  1138. part.on('data', (buffer) => {
  1139. this._fieldsSize += buffer.length;
  1140. if (this._fieldsSize > this.options.maxFieldsSize) {
  1141. this._error(
  1142. new FormidableError(
  1143. `options.maxFieldsSize (${this.options.maxFieldsSize} bytes) exceeded, received ${this._fieldsSize} bytes of field data`,
  1144. maxFieldsSizeExceeded,
  1145. 413, // Payload Too Large
  1146. ),
  1147. );
  1148. return;
  1149. }
  1150. value += decoder.write(buffer);
  1151. });
  1152. part.on('end', () => {
  1153. this.emit('field', part.name, value);
  1154. });
  1155. return;
  1156. }
  1157. if (!this.options.filter(part)) {
  1158. return;
  1159. }
  1160. this._flushing += 1;
  1161. let fileSize = 0;
  1162. const newFilename = this._getNewName(part);
  1163. const filepath = this._joinDirectoryName(newFilename);
  1164. const file = await this._newFile({
  1165. newFilename,
  1166. filepath,
  1167. originalFilename: part.originalFilename,
  1168. mimetype: part.mimetype,
  1169. });
  1170. file.on('error', (err) => {
  1171. this._error(err);
  1172. });
  1173. this.emit('fileBegin', part.name, file);
  1174. file.open();
  1175. this.openedFiles.push(file);
  1176. part.on('data', (buffer) => {
  1177. this._totalFileSize += buffer.length;
  1178. fileSize += buffer.length;
  1179. if (this._totalFileSize > this.options.maxTotalFileSize) {
  1180. this._error(
  1181. new FormidableError(
  1182. `options.maxTotalFileSize (${this.options.maxTotalFileSize} bytes) exceeded, received ${this._totalFileSize} bytes of file data`,
  1183. biggerThanTotalMaxFileSize,
  1184. 413,
  1185. ),
  1186. );
  1187. return;
  1188. }
  1189. if (buffer.length === 0) {
  1190. return;
  1191. }
  1192. this.pause();
  1193. file.write(buffer, () => {
  1194. this.resume();
  1195. });
  1196. });
  1197. part.on('end', () => {
  1198. if (!this.options.allowEmptyFiles && fileSize === 0) {
  1199. this._error(
  1200. new FormidableError(
  1201. `options.allowEmptyFiles is false, file size should be greater than 0`,
  1202. noEmptyFiles,
  1203. 400,
  1204. ),
  1205. );
  1206. return;
  1207. }
  1208. if (fileSize < this.options.minFileSize) {
  1209. this._error(
  1210. new FormidableError(
  1211. `options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${fileSize} bytes of file data`,
  1212. smallerThanMinFileSize,
  1213. 400,
  1214. ),
  1215. );
  1216. return;
  1217. }
  1218. if (fileSize > this.options.maxFileSize) {
  1219. this._error(
  1220. new FormidableError(
  1221. `options.maxFileSize (${this.options.maxFileSize} bytes), received ${fileSize} bytes of file data`,
  1222. biggerThanMaxFileSize,
  1223. 413,
  1224. ),
  1225. );
  1226. return;
  1227. }
  1228. file.end(() => {
  1229. this._flushing -= 1;
  1230. this.emit('file', part.name, file);
  1231. this._maybeEnd();
  1232. });
  1233. });
  1234. }
  1235. // eslint-disable-next-line max-statements
  1236. async _parseContentType() {
  1237. if (this.bytesExpected === 0) {
  1238. this._parser = new DummyParser(this, this.options);
  1239. return;
  1240. }
  1241. if (!this.headers['content-type']) {
  1242. this._error(
  1243. new FormidableError(
  1244. 'bad content-type header, no content-type',
  1245. missingContentType,
  1246. 400,
  1247. ),
  1248. );
  1249. return;
  1250. }
  1251. new DummyParser(this, this.options);
  1252. const results = [];
  1253. await Promise.all(this._plugins.map(async (plugin, idx) => {
  1254. let pluginReturn = null;
  1255. try {
  1256. pluginReturn = await plugin(this, this.options) || this;
  1257. } catch (err) {
  1258. // directly throw from the `form.parse` method;
  1259. // there is no other better way, except a handle through options
  1260. const error = new FormidableError(
  1261. `plugin on index ${idx} failed with: ${err.message}`,
  1262. pluginFailed,
  1263. 500,
  1264. );
  1265. error.idx = idx;
  1266. throw error;
  1267. }
  1268. Object.assign(this, pluginReturn);
  1269. // todo: use Set/Map and pass plugin name instead of the `idx` index
  1270. this.emit('plugin', idx, pluginReturn);
  1271. }));
  1272. this.emit('pluginsResults', results);
  1273. }
  1274. _error(err, eventName = 'error') {
  1275. if (this.error || this.ended) {
  1276. return;
  1277. }
  1278. this.req = null;
  1279. this.error = err;
  1280. this.emit(eventName, err);
  1281. this.openedFiles.forEach((file) => {
  1282. file.destroy();
  1283. });
  1284. }
  1285. _parseContentLength() {
  1286. this.bytesReceived = 0;
  1287. if (this.headers['content-length']) {
  1288. this.bytesExpected = parseInt(this.headers['content-length'], 10);
  1289. } else if (this.headers['transfer-encoding'] === undefined) {
  1290. this.bytesExpected = 0;
  1291. }
  1292. if (this.bytesExpected !== null) {
  1293. this.emit('progress', this.bytesReceived, this.bytesExpected);
  1294. }
  1295. }
  1296. _newParser() {
  1297. return new MultipartParser$1(this.options);
  1298. }
  1299. async _newFile({ filepath, originalFilename, mimetype, newFilename }) {
  1300. if (this.options.fileWriteStreamHandler) {
  1301. return new VolatileFile({
  1302. newFilename,
  1303. filepath,
  1304. originalFilename,
  1305. mimetype,
  1306. createFileWriteStream: this.options.fileWriteStreamHandler,
  1307. hashAlgorithm: this.options.hashAlgorithm,
  1308. });
  1309. }
  1310. if (this.options.createDirsFromUploads) {
  1311. try {
  1312. await createNecessaryDirectoriesAsync(filepath);
  1313. } catch (errorCreatingDir) {
  1314. this._error(new FormidableError(
  1315. `cannot create directory`,
  1316. cannotCreateDir,
  1317. 409,
  1318. ));
  1319. }
  1320. }
  1321. return new PersistentFile({
  1322. newFilename,
  1323. filepath,
  1324. originalFilename,
  1325. mimetype,
  1326. hashAlgorithm: this.options.hashAlgorithm,
  1327. });
  1328. }
  1329. _getFileName(headerValue) {
  1330. // matches either a quoted-string or a token (RFC 2616 section 19.5.1)
  1331. const m = headerValue.match(
  1332. /\bfilename=("(.*?)"|([^()<>{}[\]@,;:"?=\s/\t]+))($|;\s)/i,
  1333. );
  1334. if (!m) return null;
  1335. const match = m[2] || m[3] || '';
  1336. let originalFilename = match.substr(match.lastIndexOf('\\') + 1);
  1337. originalFilename = originalFilename.replace(/%22/g, '"');
  1338. originalFilename = originalFilename.replace(/&#([\d]{4});/g, (_, code) =>
  1339. String.fromCharCode(code),
  1340. );
  1341. return originalFilename;
  1342. }
  1343. // able to get composed extension with multiple dots
  1344. // "a.b.c" -> ".b.c"
  1345. // as opposed to path.extname -> ".c"
  1346. _getExtension(str) {
  1347. if (!str) {
  1348. return '';
  1349. }
  1350. const basename = path.basename(str);
  1351. const firstDot = basename.indexOf('.');
  1352. const lastDot = basename.lastIndexOf('.');
  1353. let rawExtname = path.extname(basename);
  1354. if (firstDot !== lastDot) {
  1355. rawExtname = basename.slice(firstDot);
  1356. }
  1357. let filtered;
  1358. const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar);
  1359. if (firstInvalidIndex === -1) {
  1360. filtered = rawExtname;
  1361. } else {
  1362. filtered = rawExtname.substring(0, firstInvalidIndex);
  1363. }
  1364. if (filtered === '.') {
  1365. return '';
  1366. }
  1367. return filtered;
  1368. }
  1369. _joinDirectoryName(name) {
  1370. const newPath = path.join(this.uploadDir, name);
  1371. // prevent directory traversal attacks
  1372. if (!newPath.startsWith(this.uploadDir)) {
  1373. return path.join(this.uploadDir, this.options.defaultInvalidName);
  1374. }
  1375. return newPath;
  1376. }
  1377. _setUpRename() {
  1378. const hasRename = typeof this.options.filename === 'function';
  1379. if (hasRename) {
  1380. this._getNewName = (part) => {
  1381. let ext = '';
  1382. let name = this.options.defaultInvalidName;
  1383. if (part.originalFilename) {
  1384. // can be null
  1385. ({ ext, name } = path.parse(part.originalFilename));
  1386. if (this.options.keepExtensions !== true) {
  1387. ext = '';
  1388. }
  1389. }
  1390. return this.options.filename.call(this, name, ext, part, this);
  1391. };
  1392. } else {
  1393. this._getNewName = (part) => {
  1394. const name = createId();
  1395. if (part && this.options.keepExtensions) {
  1396. const originalFilename =
  1397. typeof part === 'string' ? part : part.originalFilename;
  1398. return `${name}${this._getExtension(originalFilename)}`;
  1399. }
  1400. return name;
  1401. };
  1402. }
  1403. }
  1404. _setUpMaxFields() {
  1405. if (this.options.maxFields !== Infinity) {
  1406. let fieldsCount = 0;
  1407. this.on('field', () => {
  1408. fieldsCount += 1;
  1409. if (fieldsCount > this.options.maxFields) {
  1410. this._error(
  1411. new FormidableError(
  1412. `options.maxFields (${this.options.maxFields}) exceeded`,
  1413. maxFieldsExceeded,
  1414. 413,
  1415. ),
  1416. );
  1417. }
  1418. });
  1419. }
  1420. }
  1421. _setUpMaxFiles() {
  1422. if (this.options.maxFiles !== Infinity) {
  1423. let fileCount = 0;
  1424. this.on('fileBegin', () => {
  1425. fileCount += 1;
  1426. if (fileCount > this.options.maxFiles) {
  1427. this._error(
  1428. new FormidableError(
  1429. `options.maxFiles (${this.options.maxFiles}) exceeded`,
  1430. maxFilesExceeded,
  1431. 413,
  1432. ),
  1433. );
  1434. }
  1435. });
  1436. }
  1437. }
  1438. _maybeEnd() {
  1439. if (!this.ended || this._flushing || this.error) {
  1440. return;
  1441. }
  1442. this.req = null;
  1443. this.emit('end');
  1444. }
  1445. }
  1446. // make it available without requiring the `new` keyword
  1447. // if you want it access `const formidable.IncomingForm` as v1
  1448. const formidable = (...args) => new IncomingForm(...args);
  1449. const {enabledPlugins} = DEFAULT_OPTIONS;
  1450. exports.DummyParser = DummyParser;
  1451. exports.File = PersistentFile;
  1452. exports.Formidable = IncomingForm;
  1453. exports.IncomingForm = IncomingForm;
  1454. exports.JSONParser = JSONParser;
  1455. exports.MultipartParser = MultipartParser$1;
  1456. exports.OctetStreamParser = OctetStreamParser;
  1457. exports.OctetstreamParser = OctetStreamParser;
  1458. exports.PersistentFile = PersistentFile;
  1459. exports.QueryStringParser = QuerystringParser;
  1460. exports.QuerystringParser = QuerystringParser;
  1461. exports.VolatileFile = VolatileFile;
  1462. exports.default = formidable;
  1463. exports.defaultOptions = DEFAULT_OPTIONS;
  1464. exports.enabledPlugins = enabledPlugins;
  1465. exports.errors = FormidableError$1;
  1466. exports.formidable = formidable;
  1467. exports.json = plugin;
  1468. exports.multipart = plugin$1;
  1469. exports.octetstream = plugin$3;
  1470. exports.querystring = plugin$2;