You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
6.2 KiB

  1. var WritableStream = require('stream').Writable,
  2. inherits = require('util').inherits;
  3. var StreamSearch = require('streamsearch');
  4. var PartStream = require('./PartStream'),
  5. HeaderParser = require('./HeaderParser');
  6. var DASH = 45,
  7. B_ONEDASH = Buffer.from('-'),
  8. B_CRLF = Buffer.from('\r\n'),
  9. EMPTY_FN = function() {};
  10. function Dicer(cfg) {
  11. if (!(this instanceof Dicer))
  12. return new Dicer(cfg);
  13. WritableStream.call(this, cfg);
  14. if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string'))
  15. throw new TypeError('Boundary required');
  16. if (typeof cfg.boundary === 'string')
  17. this.setBoundary(cfg.boundary);
  18. else
  19. this._bparser = undefined;
  20. this._headerFirst = cfg.headerFirst;
  21. var self = this;
  22. this._dashes = 0;
  23. this._parts = 0;
  24. this._finished = false;
  25. this._realFinish = false;
  26. this._isPreamble = true;
  27. this._justMatched = false;
  28. this._firstWrite = true;
  29. this._inHeader = true;
  30. this._part = undefined;
  31. this._cb = undefined;
  32. this._ignoreData = false;
  33. this._partOpts = (typeof cfg.partHwm === 'number'
  34. ? { highWaterMark: cfg.partHwm }
  35. : {});
  36. this._pause = false;
  37. this._hparser = new HeaderParser(cfg);
  38. this._hparser.on('header', function(header) {
  39. self._inHeader = false;
  40. self._part.emit('header', header);
  41. });
  42. }
  43. inherits(Dicer, WritableStream);
  44. Dicer.prototype.emit = function(ev) {
  45. if (ev === 'finish' && !this._realFinish) {
  46. if (!this._finished) {
  47. var self = this;
  48. process.nextTick(function() {
  49. self.emit('error', new Error('Unexpected end of multipart data'));
  50. if (self._part && !self._ignoreData) {
  51. var type = (self._isPreamble ? 'Preamble' : 'Part');
  52. self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data'));
  53. self._part.push(null);
  54. process.nextTick(function() {
  55. self._realFinish = true;
  56. self.emit('finish');
  57. self._realFinish = false;
  58. });
  59. return;
  60. }
  61. self._realFinish = true;
  62. self.emit('finish');
  63. self._realFinish = false;
  64. });
  65. }
  66. } else
  67. WritableStream.prototype.emit.apply(this, arguments);
  68. };
  69. Dicer.prototype._write = function(data, encoding, cb) {
  70. // ignore unexpected data (e.g. extra trailer data after finished)
  71. if (!this._hparser && !this._bparser)
  72. return cb();
  73. if (this._headerFirst && this._isPreamble) {
  74. if (!this._part) {
  75. this._part = new PartStream(this._partOpts);
  76. if (this._events.preamble)
  77. this.emit('preamble', this._part);
  78. else
  79. this._ignore();
  80. }
  81. var r = this._hparser.push(data);
  82. if (!this._inHeader && r !== undefined && r < data.length)
  83. data = data.slice(r);
  84. else
  85. return cb();
  86. }
  87. // allows for "easier" testing
  88. if (this._firstWrite) {
  89. this._bparser.push(B_CRLF);
  90. this._firstWrite = false;
  91. }
  92. this._bparser.push(data);
  93. if (this._pause)
  94. this._cb = cb;
  95. else
  96. cb();
  97. };
  98. Dicer.prototype.reset = function() {
  99. this._part = undefined;
  100. this._bparser = undefined;
  101. this._hparser = undefined;
  102. };
  103. Dicer.prototype.setBoundary = function(boundary) {
  104. var self = this;
  105. this._bparser = new StreamSearch('\r\n--' + boundary);
  106. this._bparser.on('info', function(isMatch, data, start, end) {
  107. self._oninfo(isMatch, data, start, end);
  108. });
  109. };
  110. Dicer.prototype._ignore = function() {
  111. if (this._part && !this._ignoreData) {
  112. this._ignoreData = true;
  113. this._part.on('error', EMPTY_FN);
  114. // we must perform some kind of read on the stream even though we are
  115. // ignoring the data, otherwise node's Readable stream will not emit 'end'
  116. // after pushing null to the stream
  117. this._part.resume();
  118. }
  119. };
  120. Dicer.prototype._oninfo = function(isMatch, data, start, end) {
  121. var buf, self = this, i = 0, r, ev, shouldWriteMore = true;
  122. if (!this._part && this._justMatched && data) {
  123. while (this._dashes < 2 && (start + i) < end) {
  124. if (data[start + i] === DASH) {
  125. ++i;
  126. ++this._dashes;
  127. } else {
  128. if (this._dashes)
  129. buf = B_ONEDASH;
  130. this._dashes = 0;
  131. break;
  132. }
  133. }
  134. if (this._dashes === 2) {
  135. if ((start + i) < end && this._events.trailer)
  136. this.emit('trailer', data.slice(start + i, end));
  137. this.reset();
  138. this._finished = true;
  139. // no more parts will be added
  140. if (self._parts === 0) {
  141. self._realFinish = true;
  142. self.emit('finish');
  143. self._realFinish = false;
  144. }
  145. }
  146. if (this._dashes)
  147. return;
  148. }
  149. if (this._justMatched)
  150. this._justMatched = false;
  151. if (!this._part) {
  152. this._part = new PartStream(this._partOpts);
  153. this._part._read = function(n) {
  154. self._unpause();
  155. };
  156. ev = this._isPreamble ? 'preamble' : 'part';
  157. if (this._events[ev])
  158. this.emit(ev, this._part);
  159. else
  160. this._ignore();
  161. if (!this._isPreamble)
  162. this._inHeader = true;
  163. }
  164. if (data && start < end && !this._ignoreData) {
  165. if (this._isPreamble || !this._inHeader) {
  166. if (buf)
  167. shouldWriteMore = this._part.push(buf);
  168. shouldWriteMore = this._part.push(data.slice(start, end));
  169. if (!shouldWriteMore)
  170. this._pause = true;
  171. } else if (!this._isPreamble && this._inHeader) {
  172. if (buf)
  173. this._hparser.push(buf);
  174. r = this._hparser.push(data.slice(start, end));
  175. if (!this._inHeader && r !== undefined && r < end)
  176. this._oninfo(false, data, start + r, end);
  177. }
  178. }
  179. if (isMatch) {
  180. this._hparser.reset();
  181. if (this._isPreamble)
  182. this._isPreamble = false;
  183. else {
  184. ++this._parts;
  185. this._part.on('end', function() {
  186. if (--self._parts === 0) {
  187. if (self._finished) {
  188. self._realFinish = true;
  189. self.emit('finish');
  190. self._realFinish = false;
  191. } else {
  192. self._unpause();
  193. }
  194. }
  195. });
  196. }
  197. this._part.push(null);
  198. this._part = undefined;
  199. this._ignoreData = false;
  200. this._justMatched = true;
  201. this._dashes = 0;
  202. }
  203. };
  204. Dicer.prototype._unpause = function() {
  205. if (!this._pause)
  206. return;
  207. this._pause = false;
  208. if (this._cb) {
  209. var cb = this._cb;
  210. this._cb = undefined;
  211. cb();
  212. }
  213. };
  214. module.exports = Dicer;