Multipart.cjs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var node_stream = require('node:stream');
  4. const malformedMultipart = 1012;
  5. const FormidableError = class extends Error {
  6. constructor(message, internalCode, httpCode = 500) {
  7. super(message);
  8. this.code = internalCode;
  9. this.httpCode = httpCode;
  10. }
  11. };
  12. /* eslint-disable no-fallthrough */
  13. /* eslint-disable no-bitwise */
  14. /* eslint-disable no-plusplus */
  15. /* eslint-disable no-underscore-dangle */
  16. let s = 0;
  17. const STATE = {
  18. PARSER_UNINITIALIZED: s++,
  19. START: s++,
  20. START_BOUNDARY: s++,
  21. HEADER_FIELD_START: s++,
  22. HEADER_FIELD: s++,
  23. HEADER_VALUE_START: s++,
  24. HEADER_VALUE: s++,
  25. HEADER_VALUE_ALMOST_DONE: s++,
  26. HEADERS_ALMOST_DONE: s++,
  27. PART_DATA_START: s++,
  28. PART_DATA: s++,
  29. PART_END: s++,
  30. END: s++,
  31. };
  32. let f = 1;
  33. const FBOUNDARY = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) };
  34. const LF = 10;
  35. const CR = 13;
  36. const SPACE = 32;
  37. const HYPHEN = 45;
  38. const COLON = 58;
  39. const A = 97;
  40. const Z = 122;
  41. function lower(c) {
  42. return c | 0x20;
  43. }
  44. const STATES = {};
  45. Object.keys(STATE).forEach((stateName) => {
  46. STATES[stateName] = STATE[stateName];
  47. });
  48. class MultipartParser extends node_stream.Transform {
  49. constructor(options = {}) {
  50. super({ readableObjectMode: true });
  51. this.boundary = null;
  52. this.boundaryChars = null;
  53. this.lookbehind = null;
  54. this.bufferLength = 0;
  55. this.state = STATE.PARSER_UNINITIALIZED;
  56. this.globalOptions = { ...options };
  57. this.index = null;
  58. this.flags = 0;
  59. }
  60. _endUnexpected() {
  61. return new FormidableError(
  62. `MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
  63. malformedMultipart,
  64. 400,
  65. );
  66. }
  67. _flush(done) {
  68. if (
  69. (this.state === STATE.HEADER_FIELD_START && this.index === 0) ||
  70. (this.state === STATE.PART_DATA && this.index === this.boundary.length)
  71. ) {
  72. this._handleCallback('partEnd');
  73. this._handleCallback('end');
  74. done();
  75. } else if (this.state !== STATE.END) {
  76. done(this._endUnexpected());
  77. } else {
  78. done();
  79. }
  80. }
  81. initWithBoundary(str) {
  82. this.boundary = Buffer.from(`\r\n--${str}`);
  83. this.lookbehind = Buffer.alloc(this.boundary.length + 8);
  84. this.state = STATE.START;
  85. this.boundaryChars = {};
  86. for (let i = 0; i < this.boundary.length; i++) {
  87. this.boundaryChars[this.boundary[i]] = true;
  88. }
  89. }
  90. // eslint-disable-next-line max-params
  91. _handleCallback(name, buf, start, end) {
  92. if (start !== undefined && start === end) {
  93. return;
  94. }
  95. this.push({ name, buffer: buf, start, end });
  96. }
  97. // eslint-disable-next-line max-statements
  98. _transform(buffer, _, done) {
  99. let i = 0;
  100. let prevIndex = this.index;
  101. let { index, state, flags } = this;
  102. const { lookbehind, boundary, boundaryChars } = this;
  103. const boundaryLength = boundary.length;
  104. const boundaryEnd = boundaryLength - 1;
  105. this.bufferLength = buffer.length;
  106. let c = null;
  107. let cl = null;
  108. const setMark = (name, idx) => {
  109. this[`${name}Mark`] = typeof idx === 'number' ? idx : i;
  110. };
  111. const clearMarkSymbol = (name) => {
  112. delete this[`${name}Mark`];
  113. };
  114. const dataCallback = (name, shouldClear) => {
  115. const markSymbol = `${name}Mark`;
  116. if (!(markSymbol in this)) {
  117. return;
  118. }
  119. if (!shouldClear) {
  120. this._handleCallback(name, buffer, this[markSymbol], buffer.length);
  121. setMark(name, 0);
  122. } else {
  123. this._handleCallback(name, buffer, this[markSymbol], i);
  124. clearMarkSymbol(name);
  125. }
  126. };
  127. for (i = 0; i < this.bufferLength; i++) {
  128. c = buffer[i];
  129. switch (state) {
  130. case STATE.PARSER_UNINITIALIZED:
  131. done(this._endUnexpected());
  132. return;
  133. case STATE.START:
  134. index = 0;
  135. state = STATE.START_BOUNDARY;
  136. case STATE.START_BOUNDARY:
  137. if (index === boundary.length - 2) {
  138. if (c === HYPHEN) {
  139. flags |= FBOUNDARY.LAST_BOUNDARY;
  140. } else if (c !== CR) {
  141. done(this._endUnexpected());
  142. return;
  143. }
  144. index++;
  145. break;
  146. } else if (index - 1 === boundary.length - 2) {
  147. if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) {
  148. this._handleCallback('end');
  149. state = STATE.END;
  150. flags = 0;
  151. } else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) {
  152. index = 0;
  153. this._handleCallback('partBegin');
  154. state = STATE.HEADER_FIELD_START;
  155. } else {
  156. done(this._endUnexpected());
  157. return;
  158. }
  159. break;
  160. }
  161. if (c !== boundary[index + 2]) {
  162. index = -2;
  163. }
  164. if (c === boundary[index + 2]) {
  165. index++;
  166. }
  167. break;
  168. case STATE.HEADER_FIELD_START:
  169. state = STATE.HEADER_FIELD;
  170. setMark('headerField');
  171. index = 0;
  172. case STATE.HEADER_FIELD:
  173. if (c === CR) {
  174. clearMarkSymbol('headerField');
  175. state = STATE.HEADERS_ALMOST_DONE;
  176. break;
  177. }
  178. index++;
  179. if (c === HYPHEN) {
  180. break;
  181. }
  182. if (c === COLON) {
  183. if (index === 1) {
  184. // empty header field
  185. done(this._endUnexpected());
  186. return;
  187. }
  188. dataCallback('headerField', true);
  189. state = STATE.HEADER_VALUE_START;
  190. break;
  191. }
  192. cl = lower(c);
  193. if (cl < A || cl > Z) {
  194. done(this._endUnexpected());
  195. return;
  196. }
  197. break;
  198. case STATE.HEADER_VALUE_START:
  199. if (c === SPACE) {
  200. break;
  201. }
  202. setMark('headerValue');
  203. state = STATE.HEADER_VALUE;
  204. case STATE.HEADER_VALUE:
  205. if (c === CR) {
  206. dataCallback('headerValue', true);
  207. this._handleCallback('headerEnd');
  208. state = STATE.HEADER_VALUE_ALMOST_DONE;
  209. }
  210. break;
  211. case STATE.HEADER_VALUE_ALMOST_DONE:
  212. if (c !== LF) {
  213. done(this._endUnexpected());
  214. return;
  215. }
  216. state = STATE.HEADER_FIELD_START;
  217. break;
  218. case STATE.HEADERS_ALMOST_DONE:
  219. if (c !== LF) {
  220. done(this._endUnexpected());
  221. return;
  222. }
  223. this._handleCallback('headersEnd');
  224. state = STATE.PART_DATA_START;
  225. break;
  226. case STATE.PART_DATA_START:
  227. state = STATE.PART_DATA;
  228. setMark('partData');
  229. case STATE.PART_DATA:
  230. prevIndex = index;
  231. if (index === 0) {
  232. // boyer-moore derived algorithm to safely skip non-boundary data
  233. i += boundaryEnd;
  234. while (i < this.bufferLength && !(buffer[i] in boundaryChars)) {
  235. i += boundaryLength;
  236. }
  237. i -= boundaryEnd;
  238. c = buffer[i];
  239. }
  240. if (index < boundary.length) {
  241. if (boundary[index] === c) {
  242. if (index === 0) {
  243. dataCallback('partData', true);
  244. }
  245. index++;
  246. } else {
  247. index = 0;
  248. }
  249. } else if (index === boundary.length) {
  250. index++;
  251. if (c === CR) {
  252. // CR = part boundary
  253. flags |= FBOUNDARY.PART_BOUNDARY;
  254. } else if (c === HYPHEN) {
  255. // HYPHEN = end boundary
  256. flags |= FBOUNDARY.LAST_BOUNDARY;
  257. } else {
  258. index = 0;
  259. }
  260. } else if (index - 1 === boundary.length) {
  261. if (flags & FBOUNDARY.PART_BOUNDARY) {
  262. index = 0;
  263. if (c === LF) {
  264. // unset the PART_BOUNDARY flag
  265. flags &= ~FBOUNDARY.PART_BOUNDARY;
  266. this._handleCallback('partEnd');
  267. this._handleCallback('partBegin');
  268. state = STATE.HEADER_FIELD_START;
  269. break;
  270. }
  271. } else if (flags & FBOUNDARY.LAST_BOUNDARY) {
  272. if (c === HYPHEN) {
  273. this._handleCallback('partEnd');
  274. this._handleCallback('end');
  275. state = STATE.END;
  276. flags = 0;
  277. } else {
  278. index = 0;
  279. }
  280. } else {
  281. index = 0;
  282. }
  283. }
  284. if (index > 0) {
  285. // when matching a possible boundary, keep a lookbehind reference
  286. // in case it turns out to be a false lead
  287. lookbehind[index - 1] = c;
  288. } else if (prevIndex > 0) {
  289. // if our boundary turned out to be rubbish, the captured lookbehind
  290. // belongs to partData
  291. this._handleCallback('partData', lookbehind, 0, prevIndex);
  292. prevIndex = 0;
  293. setMark('partData');
  294. // reconsider the current character even so it interrupted the sequence
  295. // it could be the beginning of a new sequence
  296. i--;
  297. }
  298. break;
  299. case STATE.END:
  300. break;
  301. default:
  302. done(this._endUnexpected());
  303. return;
  304. }
  305. }
  306. dataCallback('headerField');
  307. dataCallback('headerValue');
  308. dataCallback('partData');
  309. this.index = index;
  310. this.state = state;
  311. this.flags = flags;
  312. done();
  313. return this.bufferLength;
  314. }
  315. explain() {
  316. return `state = ${MultipartParser.stateToString(this.state)}`;
  317. }
  318. }
  319. // eslint-disable-next-line consistent-return
  320. MultipartParser.stateToString = (stateNumber) => {
  321. // eslint-disable-next-line no-restricted-syntax, guard-for-in
  322. for (const stateName in STATE) {
  323. const number = STATE[stateName];
  324. if (number === stateNumber) return stateName;
  325. }
  326. };
  327. var Multipart = Object.assign(MultipartParser, { STATES });
  328. exports.STATES = STATES;
  329. exports.default = Multipart;