|                                                                                                                                                                                                                                                                                                                                                                                                               |  | /** * Form Data format: *
```txt
--FormStreamBoundary1349886663601\r\nContent-Disposition: form-data; name="foo"\r\n\r\n<FIELD-CONTENT>\r\n--FormStreamBoundary1349886663601\r\nContent-Disposition: form-data; name="data"\r\nContent-Type: application/json\r\n\r\n<JSON-FORMAT-CONTENT>\r\n--FormStreamBoundary1349886663601\r\nContent-Disposition: form-data; name="file"; filename="formstream.test.js"\r\nContent-Type: application/javascript\r\n\r\n<FILE-CONTENT-CHUNK-1>...<FILE-CONTENT-CHUNK-N>\r\n--FormStreamBoundary1349886663601\r\nContent-Disposition: form-data; name="pic"; filename="fawave.png"\r\nContent-Type: image/png\r\n\r\n<IMAGE-CONTENT>\r\n--FormStreamBoundary1349886663601--```
 * */
'use strict';
var debug = require('util').debuglog('formstream');var Stream = require('stream');var parseStream = require('pause-stream');var util = require('util');var mime = require('mime');var path = require('path');var fs = require('fs');var destroy = require('destroy');var hex = require('node-hex');
var PADDING = '--';var NEW_LINE = '\r\n';var NEW_LINE_BUFFER =  Buffer.from(NEW_LINE);
function FormStream(options) {  if (!(this instanceof FormStream)) {    return new FormStream(options);  }
  FormStream.super_.call(this);
  this._boundary = this._generateBoundary();  this._streams = [];  this._buffers = [];  this._endData = Buffer.from(PADDING + this._boundary + PADDING + NEW_LINE);  this._contentLength = 0;  this._isAllStreamSizeKnown = true;  this._knownStreamSize = 0;  this._minChunkSize = options && options.minChunkSize || 0;
  this.isFormStream = true;  debug('start boundary\n%s', this._boundary);}
util.inherits(FormStream, Stream);module.exports = FormStream;
FormStream.prototype._generateBoundary = function() {  // https://github.com/felixge/node-form-data/blob/master/lib/form_data.js#L162
  // This generates a 50 character boundary similar to those used by Firefox.
  // They are optimized for boyer-moore parsing.
  var boundary = '--------------------------';  for (var i = 0; i < 24; i++) {    boundary += Math.floor(Math.random() * 10).toString(16);  }
  return boundary;};
FormStream.prototype.setTotalStreamSize = function (size) {  // this method should not make any sense if the length of each stream is known.
  if (this._isAllStreamSizeKnown) {    return this;  }
  size = size || 0;
  for (var i = 0; i < this._streams.length; i++) {    size += this._streams[i][0].length;    size += NEW_LINE_BUFFER.length; // stream field end padding size
  }
  this._knownStreamSize = size;  this._isAllStreamSizeKnown = true;  debug('set total size: %s', size);  return this;};
FormStream.prototype.headers = function (options) {  var headers = {    'Content-Type': 'multipart/form-data; boundary=' + this._boundary  };
  // calculate total stream size
  this._contentLength += this._knownStreamSize;  // calculate length of end padding
  this._contentLength += this._endData.length;
  if (this._isAllStreamSizeKnown) {    headers['Content-Length'] = String(this._contentLength);  }
  if (options) {    for (var k in options) {      headers[k] = options[k];    }  }
  debug('headers: %j', headers);  return headers;};
FormStream.prototype.file = function (name, filepath, filename, filesize) {  if (typeof filename === 'number' && !filesize) {    filesize = filename;    filename = path.basename(filepath);  }  if (!filename) {    filename = path.basename(filepath);  }
  var mimeType = mime.getType(filename);  var stream = fs.createReadStream(filepath);
  return this.stream(name, stream, filename, mimeType, filesize);};
/** * Add a form field * @param  {String} name field name * @param  {String|Buffer} value field value * @param  {String} [mimeType] field mimeType * @return {this} */FormStream.prototype.field = function (name, value, mimeType) {  if (!Buffer.isBuffer(value)) {    // field(String, Number)
    // https://github.com/qiniu/nodejs-sdk/issues/123
    if (typeof value === 'number') {      value = String(value);    }    value = Buffer.from(value);  }  return this.buffer(name, value, null, mimeType);};
FormStream.prototype.stream = function (name, stream, filename, mimeType, size) {  if (typeof mimeType === 'number' && !size) {    size = mimeType;    mimeType = mime.getType(filename);  } else if (!mimeType) {    mimeType = mime.getType(filename);  }
  stream.once('error', this.emit.bind(this, 'error'));  // if form stream destroy, also destroy the source stream
  this.once('destroy', function () {    destroy(stream);  });
  var leading = this._leading({ name: name, filename: filename }, mimeType);
  var ps = parseStream().pause();  stream.pipe(ps);
  this._streams.push([leading, ps]);
  // if the size of this stream is known, plus the total content-length;
  // otherwise, content-length is unknown.
  if (typeof size === 'number') {    this._knownStreamSize += leading.length;    this._knownStreamSize += size;    this._knownStreamSize += NEW_LINE_BUFFER.length;  } else {    this._isAllStreamSizeKnown = false;  }
  process.nextTick(this.resume.bind(this));
  return this;};
FormStream.prototype.buffer = function (name, buffer, filename, mimeType) {  if (filename && !mimeType) {    mimeType = mime.getType(filename);  }
  var disposition = { name: name };  if (filename) {    disposition.filename = filename;  }
  var leading = this._leading(disposition, mimeType);
  // plus buffer length to total content-length
  var bufferSize = leading.length + buffer.length + NEW_LINE_BUFFER.length;  this._buffers.push(Buffer.concat([leading, buffer, NEW_LINE_BUFFER], bufferSize));    this._contentLength += bufferSize;
  process.nextTick(this.resume.bind(this));  if (debug.enabled) {    if (buffer.length > 512) {      debug('new buffer field, content size: %d\n%s%s',        buffer.length, leading.toString(), hex(buffer.slice(0, 512)));    } else {      debug('new buffer field, content size: %d\n%s%s',        buffer.length, leading.toString(), hex(buffer));    }  }  return this;};
FormStream.prototype._leading = function (disposition, type) {  var leading = [PADDING + this._boundary];
  var dispositions = [];
  if (disposition) {    for (var k in disposition) {      dispositions.push(k + '="' + disposition[k] + '"');    }  }
  leading.push('Content-Disposition: form-data; ' + dispositions.join('; '));  if (type) {    leading.push('Content-Type: ' + type);  }
  leading.push('');  leading.push('');  return Buffer.from(leading.join(NEW_LINE));};
FormStream.prototype._emitBuffers = function () {  if (!this._buffers.length) {    return;  }
  for (var i = 0; i < this._buffers.length; i++) {    this.emit('data', this._buffers[i]);  }  this._buffers = [];};
FormStream.prototype._emitStream = function (item) {  var self = this;  // item: [ leading, stream ]
  var streamSize = 0;  var chunkCount = 0;  const leading = item[0];  self.emit('data', leading);  chunkCount++;  if (debug.enabled) {    debug('new stream, chunk index %d\n%s', chunkCount, leading.toString());  }
  var stream = item[1];  stream.on('data', function (data) {    self.emit('data', data);    streamSize += leading.length;    chunkCount++;    if (debug.enabled) {      if (data.length > 512) {        debug('stream chunk, size %d, chunk index %d, stream size %d\n%s......   only show 512 bytes   ......',          data.length, chunkCount, streamSize, hex(data.slice(0, 512)));      } else {        debug('stream chunk, size %d, chunk index %d, stream size %d\n%s',          data.length, chunkCount, streamSize, hex(data));      }    }  });  stream.on('end', function () {    self.emit('data', NEW_LINE_BUFFER);    chunkCount++;    debug('stream end, chunk index %d, stream size %d', chunkCount, streamSize);    return process.nextTick(self.drain.bind(self));  });  stream.resume();};
FormStream.prototype._emitStreamWithChunkSize = function (item, minChunkSize) {  var self = this;  // item: [ leading, stream ]
  var streamSize = 0;  var chunkCount = 0;  var bufferSize = 0;  var buffers = [];  const leading = item[0];  buffers.push(leading);  bufferSize += leading.length;  if (debug.enabled) {    debug('new stream, with min chunk size: %d\n%s', minChunkSize, leading.toString());  }
  var stream = item[1];  stream.on('data', function (data) {    if (typeof data === 'string') {      data = Buffer.from(data, 'utf-8');    }    buffers.push(data);    bufferSize += data.length;    streamSize += data.length;    debug('got stream data size %d, buffer size %d, stream size %d',      data.length, bufferSize, streamSize);    if (bufferSize >= minChunkSize) {      const chunk = Buffer.concat(buffers, bufferSize);      buffers = [];      bufferSize = 0;      self.emit('data', chunk);      chunkCount++;      if (debug.enabled) {        if (chunk.length > 512) {          debug('stream chunk, size %d, chunk index %d, stream size %d\n%s......   only show 512 bytes   ......',            chunk.length, chunkCount, streamSize, hex(chunk.slice(0, 512)));        } else {          debug('stream chunk, size %d, chunk index %d, stream size %d\n%s',            chunk.length, chunkCount, streamSize, hex(chunk));        }      }    }  });  stream.on('end', function () {    buffers.push(NEW_LINE_BUFFER);    bufferSize += NEW_LINE_BUFFER.length;    const chunk = Buffer.concat(buffers, bufferSize);    self.emit('data', chunk);    chunkCount++;    if (chunk.length > 512) {      debug('stream end, size %d, chunk index %d, stream size %d\n%s......   only show 512 bytes   ......',        chunk.length, chunkCount, streamSize, hex(chunk.slice(0, 512)));    } else {      debug('stream end, size %d, chunk index %d, stream size %d\n%s',        chunk.length, chunkCount, streamSize, hex(chunk));    }    return process.nextTick(self.drain.bind(self));  });  stream.resume();};
FormStream.prototype._emitEnd = function () {  // ending format:
  //
  // --{boundary}--\r\n
  this.emit('data', this._endData);  this.emit('end');  if (debug.enabled) {    debug('end boundary\n%s', this._endData.toString());  }};
FormStream.prototype.drain = function () {  // debug('drain');
  this._emitBuffers();
  var item = this._streams.shift();  if (item) {    if (this._minChunkSize && this._minChunkSize > 0) {      this._emitStreamWithChunkSize(item, this._minChunkSize);    } else {      this._emitStream(item);    }  } else {    this._emitEnd();  }
  return this;};
FormStream.prototype.resume = function () {  // debug('resume');
  this.paused = false;
  if (!this._draining) {    this._draining = true;    this.drain();  }
  return this;};
FormStream.prototype.close = FormStream.prototype.destroy = function () {  this.emit('destroy');  // debug('destroy or close');
};
 |