"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/form-data/lib/form_data.js" (25 Apr 2018, 12249 Bytes) of package /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 var CombinedStream = require('combined-stream');
    2 var util = require('util');
    3 var path = require('path');
    4 var http = require('http');
    5 var https = require('https');
    6 var parseUrl = require('url').parse;
    7 var fs = require('fs');
    8 var mime = require('mime-types');
    9 var asynckit = require('asynckit');
   10 var populate = require('./populate.js');
   11 
   12 // Public API
   13 module.exports = FormData;
   14 
   15 // make it a Stream
   16 util.inherits(FormData, CombinedStream);
   17 
   18 /**
   19  * Create readable "multipart/form-data" streams.
   20  * Can be used to submit forms
   21  * and file uploads to other web applications.
   22  *
   23  * @constructor
   24  * @param {Object} options - Properties to be added/overriden for FormData and CombinedStream
   25  */
   26 function FormData(options) {
   27   if (!(this instanceof FormData)) {
   28     return new FormData();
   29   }
   30 
   31   this._overheadLength = 0;
   32   this._valueLength = 0;
   33   this._valuesToMeasure = [];
   34 
   35   CombinedStream.call(this);
   36 
   37   options = options || {};
   38   for (var option in options) {
   39     this[option] = options[option];
   40   }
   41 }
   42 
   43 FormData.LINE_BREAK = '\r\n';
   44 FormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream';
   45 
   46 FormData.prototype.append = function(field, value, options) {
   47 
   48   options = options || {};
   49 
   50   // allow filename as single option
   51   if (typeof options == 'string') {
   52     options = {filename: options};
   53   }
   54 
   55   var append = CombinedStream.prototype.append.bind(this);
   56 
   57   // all that streamy business can't handle numbers
   58   if (typeof value == 'number') {
   59     value = '' + value;
   60   }
   61 
   62   // https://github.com/felixge/node-form-data/issues/38
   63   if (util.isArray(value)) {
   64     // Please convert your array into string
   65     // the way web server expects it
   66     this._error(new Error('Arrays are not supported.'));
   67     return;
   68   }
   69 
   70   var header = this._multiPartHeader(field, value, options);
   71   var footer = this._multiPartFooter();
   72 
   73   append(header);
   74   append(value);
   75   append(footer);
   76 
   77   // pass along options.knownLength
   78   this._trackLength(header, value, options);
   79 };
   80 
   81 FormData.prototype._trackLength = function(header, value, options) {
   82   var valueLength = 0;
   83 
   84   // used w/ getLengthSync(), when length is known.
   85   // e.g. for streaming directly from a remote server,
   86   // w/ a known file a size, and not wanting to wait for
   87   // incoming file to finish to get its size.
   88   if (options.knownLength != null) {
   89     valueLength += +options.knownLength;
   90   } else if (Buffer.isBuffer(value)) {
   91     valueLength = value.length;
   92   } else if (typeof value === 'string') {
   93     valueLength = Buffer.byteLength(value);
   94   }
   95 
   96   this._valueLength += valueLength;
   97 
   98   // @check why add CRLF? does this account for custom/multiple CRLFs?
   99   this._overheadLength +=
  100     Buffer.byteLength(header) +
  101     FormData.LINE_BREAK.length;
  102 
  103   // empty or either doesn't have path or not an http response
  104   if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {
  105     return;
  106   }
  107 
  108   // no need to bother with the length
  109   if (!options.knownLength) {
  110     this._valuesToMeasure.push(value);
  111   }
  112 };
  113 
  114 FormData.prototype._lengthRetriever = function(value, callback) {
  115 
  116   if (value.hasOwnProperty('fd')) {
  117 
  118     // take read range into a account
  119     // `end` = Infinity –> read file till the end
  120     //
  121     // TODO: Looks like there is bug in Node fs.createReadStream
  122     // it doesn't respect `end` options without `start` options
  123     // Fix it when node fixes it.
  124     // https://github.com/joyent/node/issues/7819
  125     if (value.end != undefined && value.end != Infinity && value.start != undefined) {
  126 
  127       // when end specified
  128       // no need to calculate range
  129       // inclusive, starts with 0
  130       callback(null, value.end + 1 - (value.start ? value.start : 0));
  131 
  132     // not that fast snoopy
  133     } else {
  134       // still need to fetch file size from fs
  135       fs.stat(value.path, function(err, stat) {
  136 
  137         var fileSize;
  138 
  139         if (err) {
  140           callback(err);
  141           return;
  142         }
  143 
  144         // update final size based on the range options
  145         fileSize = stat.size - (value.start ? value.start : 0);
  146         callback(null, fileSize);
  147       });
  148     }
  149 
  150   // or http response
  151   } else if (value.hasOwnProperty('httpVersion')) {
  152     callback(null, +value.headers['content-length']);
  153 
  154   // or request stream http://github.com/mikeal/request
  155   } else if (value.hasOwnProperty('httpModule')) {
  156     // wait till response come back
  157     value.on('response', function(response) {
  158       value.pause();
  159       callback(null, +response.headers['content-length']);
  160     });
  161     value.resume();
  162 
  163   // something else
  164   } else {
  165     callback('Unknown stream');
  166   }
  167 };
  168 
  169 FormData.prototype._multiPartHeader = function(field, value, options) {
  170   // custom header specified (as string)?
  171   // it becomes responsible for boundary
  172   // (e.g. to handle extra CRLFs on .NET servers)
  173   if (typeof options.header == 'string') {
  174     return options.header;
  175   }
  176 
  177   var contentDisposition = this._getContentDisposition(value, options);
  178   var contentType = this._getContentType(value, options);
  179 
  180   var contents = '';
  181   var headers  = {
  182     // add custom disposition as third element or keep it two elements if not
  183     'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
  184     // if no content type. allow it to be empty array
  185     'Content-Type': [].concat(contentType || [])
  186   };
  187 
  188   // allow custom headers.
  189   if (typeof options.header == 'object') {
  190     populate(headers, options.header);
  191   }
  192 
  193   var header;
  194   for (var prop in headers) {
  195     if (!headers.hasOwnProperty(prop)) continue;
  196     header = headers[prop];
  197 
  198     // skip nullish headers.
  199     if (header == null) {
  200       continue;
  201     }
  202 
  203     // convert all headers to arrays.
  204     if (!Array.isArray(header)) {
  205       header = [header];
  206     }
  207 
  208     // add non-empty headers.
  209     if (header.length) {
  210       contents += prop + ': ' + header.join('; ') + FormData.LINE_BREAK;
  211     }
  212   }
  213 
  214   return '--' + this.getBoundary() + FormData.LINE_BREAK + contents + FormData.LINE_BREAK;
  215 };
  216 
  217 FormData.prototype._getContentDisposition = function(value, options) {
  218 
  219   var filename
  220     , contentDisposition
  221     ;
  222 
  223   if (typeof options.filepath === 'string') {
  224     // custom filepath for relative paths
  225     filename = path.normalize(options.filepath).replace(/\\/g, '/');
  226   } else if (options.filename || value.name || value.path) {
  227     // custom filename take precedence
  228     // formidable and the browser add a name property
  229     // fs- and request- streams have path property
  230     filename = path.basename(options.filename || value.name || value.path);
  231   } else if (value.readable && value.hasOwnProperty('httpVersion')) {
  232     // or try http response
  233     filename = path.basename(value.client._httpMessage.path);
  234   }
  235 
  236   if (filename) {
  237     contentDisposition = 'filename="' + filename + '"';
  238   }
  239 
  240   return contentDisposition;
  241 };
  242 
  243 FormData.prototype._getContentType = function(value, options) {
  244 
  245   // use custom content-type above all
  246   var contentType = options.contentType;
  247 
  248   // or try `name` from formidable, browser
  249   if (!contentType && value.name) {
  250     contentType = mime.lookup(value.name);
  251   }
  252 
  253   // or try `path` from fs-, request- streams
  254   if (!contentType && value.path) {
  255     contentType = mime.lookup(value.path);
  256   }
  257 
  258   // or if it's http-reponse
  259   if (!contentType && value.readable && value.hasOwnProperty('httpVersion')) {
  260     contentType = value.headers['content-type'];
  261   }
  262 
  263   // or guess it from the filepath or filename
  264   if (!contentType && (options.filepath || options.filename)) {
  265     contentType = mime.lookup(options.filepath || options.filename);
  266   }
  267 
  268   // fallback to the default content type if `value` is not simple value
  269   if (!contentType && typeof value == 'object') {
  270     contentType = FormData.DEFAULT_CONTENT_TYPE;
  271   }
  272 
  273   return contentType;
  274 };
  275 
  276 FormData.prototype._multiPartFooter = function() {
  277   return function(next) {
  278     var footer = FormData.LINE_BREAK;
  279 
  280     var lastPart = (this._streams.length === 0);
  281     if (lastPart) {
  282       footer += this._lastBoundary();
  283     }
  284 
  285     next(footer);
  286   }.bind(this);
  287 };
  288 
  289 FormData.prototype._lastBoundary = function() {
  290   return '--' + this.getBoundary() + '--' + FormData.LINE_BREAK;
  291 };
  292 
  293 FormData.prototype.getHeaders = function(userHeaders) {
  294   var header;
  295   var formHeaders = {
  296     'content-type': 'multipart/form-data; boundary=' + this.getBoundary()
  297   };
  298 
  299   for (header in userHeaders) {
  300     if (userHeaders.hasOwnProperty(header)) {
  301       formHeaders[header.toLowerCase()] = userHeaders[header];
  302     }
  303   }
  304 
  305   return formHeaders;
  306 };
  307 
  308 FormData.prototype.getBoundary = function() {
  309   if (!this._boundary) {
  310     this._generateBoundary();
  311   }
  312 
  313   return this._boundary;
  314 };
  315 
  316 FormData.prototype._generateBoundary = function() {
  317   // This generates a 50 character boundary similar to those used by Firefox.
  318   // They are optimized for boyer-moore parsing.
  319   var boundary = '--------------------------';
  320   for (var i = 0; i < 24; i++) {
  321     boundary += Math.floor(Math.random() * 10).toString(16);
  322   }
  323 
  324   this._boundary = boundary;
  325 };
  326 
  327 // Note: getLengthSync DOESN'T calculate streams length
  328 // As workaround one can calculate file size manually
  329 // and add it as knownLength option
  330 FormData.prototype.getLengthSync = function() {
  331   var knownLength = this._overheadLength + this._valueLength;
  332 
  333   // Don't get confused, there are 3 "internal" streams for each keyval pair
  334   // so it basically checks if there is any value added to the form
  335   if (this._streams.length) {
  336     knownLength += this._lastBoundary().length;
  337   }
  338 
  339   // https://github.com/form-data/form-data/issues/40
  340   if (!this.hasKnownLength()) {
  341     // Some async length retrievers are present
  342     // therefore synchronous length calculation is false.
  343     // Please use getLength(callback) to get proper length
  344     this._error(new Error('Cannot calculate proper length in synchronous way.'));
  345   }
  346 
  347   return knownLength;
  348 };
  349 
  350 // Public API to check if length of added values is known
  351 // https://github.com/form-data/form-data/issues/196
  352 // https://github.com/form-data/form-data/issues/262
  353 FormData.prototype.hasKnownLength = function() {
  354   var hasKnownLength = true;
  355 
  356   if (this._valuesToMeasure.length) {
  357     hasKnownLength = false;
  358   }
  359 
  360   return hasKnownLength;
  361 };
  362 
  363 FormData.prototype.getLength = function(cb) {
  364   var knownLength = this._overheadLength + this._valueLength;
  365 
  366   if (this._streams.length) {
  367     knownLength += this._lastBoundary().length;
  368   }
  369 
  370   if (!this._valuesToMeasure.length) {
  371     process.nextTick(cb.bind(this, null, knownLength));
  372     return;
  373   }
  374 
  375   asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) {
  376     if (err) {
  377       cb(err);
  378       return;
  379     }
  380 
  381     values.forEach(function(length) {
  382       knownLength += length;
  383     });
  384 
  385     cb(null, knownLength);
  386   });
  387 };
  388 
  389 FormData.prototype.submit = function(params, cb) {
  390   var request
  391     , options
  392     , defaults = {method: 'post'}
  393     ;
  394 
  395   // parse provided url if it's string
  396   // or treat it as options object
  397   if (typeof params == 'string') {
  398 
  399     params = parseUrl(params);
  400     options = populate({
  401       port: params.port,
  402       path: params.pathname,
  403       host: params.hostname,
  404       protocol: params.protocol
  405     }, defaults);
  406 
  407   // use custom params
  408   } else {
  409 
  410     options = populate(params, defaults);
  411     // if no port provided use default one
  412     if (!options.port) {
  413       options.port = options.protocol == 'https:' ? 443 : 80;
  414     }
  415   }
  416 
  417   // put that good code in getHeaders to some use
  418   options.headers = this.getHeaders(params.headers);
  419 
  420   // https if specified, fallback to http in any other case
  421   if (options.protocol == 'https:') {
  422     request = https.request(options);
  423   } else {
  424     request = http.request(options);
  425   }
  426 
  427   // get content length and fire away
  428   this.getLength(function(err, length) {
  429     if (err) {
  430       this._error(err);
  431       return;
  432     }
  433 
  434     // add content length
  435     request.setHeader('Content-Length', length);
  436 
  437     this.pipe(request);
  438     if (cb) {
  439       request.on('error', cb);
  440       request.on('response', cb.bind(this, null));
  441     }
  442   }.bind(this));
  443 
  444   return request;
  445 };
  446 
  447 FormData.prototype._error = function(err) {
  448   if (!this.error) {
  449     this.error = err;
  450     this.pause();
  451     this.emit('error', err);
  452   }
  453 };
  454 
  455 FormData.prototype.toString = function () {
  456   return '[object FormData]';
  457 };