"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/form-data/lib/form_data.js" (8 Mar 2017, 11584 Bytes) of archive /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 async = require('async');
   10 
   11 module.exports = FormData;
   12 function FormData() {
   13   this._overheadLength = 0;
   14   this._valueLength = 0;
   15   this._lengthRetrievers = [];
   16 
   17   CombinedStream.call(this);
   18 }
   19 util.inherits(FormData, CombinedStream);
   20 
   21 FormData.LINE_BREAK = '\r\n';
   22 
   23 FormData.prototype.append = function(field, value, options) {
   24   options = options || {};
   25 
   26   var append = CombinedStream.prototype.append.bind(this);
   27 
   28   // all that streamy business can't handle numbers
   29   if (typeof value == 'number') value = ''+value;
   30 
   31   // https://github.com/felixge/node-form-data/issues/38
   32   if (util.isArray(value)) {
   33     // Please convert your array into string
   34     // the way web server expects it
   35     this._error(new Error('Arrays are not supported.'));
   36     return;
   37   }
   38 
   39   var header = this._multiPartHeader(field, value, options);
   40   var footer = this._multiPartFooter(field, value, options);
   41 
   42   append(header);
   43   append(value);
   44   append(footer);
   45 
   46   // pass along options.knownLength
   47   this._trackLength(header, value, options);
   48 };
   49 
   50 FormData.prototype._trackLength = function(header, value, options) {
   51   var valueLength = 0;
   52 
   53   // used w/ getLengthSync(), when length is known.
   54   // e.g. for streaming directly from a remote server,
   55   // w/ a known file a size, and not wanting to wait for
   56   // incoming file to finish to get its size.
   57   if (options.knownLength != null) {
   58     valueLength += +options.knownLength;
   59   } else if (Buffer.isBuffer(value)) {
   60     valueLength = value.length;
   61   } else if (typeof value === 'string') {
   62     valueLength = Buffer.byteLength(value);
   63   }
   64 
   65   this._valueLength += valueLength;
   66 
   67   // @check why add CRLF? does this account for custom/multiple CRLFs?
   68   this._overheadLength +=
   69     Buffer.byteLength(header) +
   70     + FormData.LINE_BREAK.length;
   71 
   72   // empty or either doesn't have path or not an http response
   73   if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {
   74     return;
   75   }
   76 
   77   // no need to bother with the length
   78   if (!options.knownLength)
   79   this._lengthRetrievers.push(function(next) {
   80 
   81     if (value.hasOwnProperty('fd')) {
   82 
   83       // take read range into a account
   84       // `end` = Infinity –> read file till the end
   85       //
   86       // TODO: Looks like there is bug in Node fs.createReadStream
   87       // it doesn't respect `end` options without `start` options
   88       // Fix it when node fixes it.
   89       // https://github.com/joyent/node/issues/7819
   90       if (value.end != undefined && value.end != Infinity && value.start != undefined) {
   91 
   92         // when end specified
   93         // no need to calculate range
   94         // inclusive, starts with 0
   95         next(null, value.end+1 - (value.start ? value.start : 0));
   96 
   97       // not that fast snoopy
   98       } else {
   99         // still need to fetch file size from fs
  100         fs.stat(value.path, function(err, stat) {
  101 
  102           var fileSize;
  103 
  104           if (err) {
  105             next(err);
  106             return;
  107           }
  108 
  109           // update final size based on the range options
  110           fileSize = stat.size - (value.start ? value.start : 0);
  111           next(null, fileSize);
  112         });
  113       }
  114 
  115     // or http response
  116     } else if (value.hasOwnProperty('httpVersion')) {
  117       next(null, +value.headers['content-length']);
  118 
  119     // or request stream http://github.com/mikeal/request
  120     } else if (value.hasOwnProperty('httpModule')) {
  121       // wait till response come back
  122       value.on('response', function(response) {
  123         value.pause();
  124         next(null, +response.headers['content-length']);
  125       });
  126       value.resume();
  127 
  128     // something else
  129     } else {
  130       next('Unknown stream');
  131     }
  132   });
  133 };
  134 
  135 FormData.prototype._multiPartHeader = function(field, value, options) {
  136   var boundary = this.getBoundary();
  137   var header = '';
  138 
  139   // custom header specified (as string)?
  140   // it becomes responsible for boundary
  141   // (e.g. to handle extra CRLFs on .NET servers)
  142   if (options.header != null) {
  143     header = options.header;
  144   } else {
  145     header += '--' + boundary + FormData.LINE_BREAK +
  146       'Content-Disposition: form-data; name="' + field + '"';
  147 
  148     // fs- and request- streams have path property
  149     // or use custom filename and/or contentType
  150     // TODO: Use request's response mime-type
  151     if (options.filename || value.path) {
  152       header +=
  153         '; filename="' + path.basename(options.filename || value.path) + '"' + FormData.LINE_BREAK +
  154         'Content-Type: ' +  (options.contentType || mime.lookup(options.filename || value.path));
  155 
  156     // http response has not
  157     } else if (value.readable && value.hasOwnProperty('httpVersion')) {
  158       header +=
  159         '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
  160         'Content-Type: ' + value.headers['content-type'];
  161     }
  162 
  163     header += FormData.LINE_BREAK + FormData.LINE_BREAK;
  164   }
  165 
  166   return header;
  167 };
  168 
  169 FormData.prototype._multiPartFooter = function(field, value, options) {
  170   return function(next) {
  171     var footer = FormData.LINE_BREAK;
  172 
  173     var lastPart = (this._streams.length === 0);
  174     if (lastPart) {
  175       footer += this._lastBoundary();
  176     }
  177 
  178     next(footer);
  179   }.bind(this);
  180 };
  181 
  182 FormData.prototype._lastBoundary = function() {
  183   return '--' + this.getBoundary() + '--';
  184 };
  185 
  186 FormData.prototype.getHeaders = function(userHeaders) {
  187   var formHeaders = {
  188     'content-type': 'multipart/form-data; boundary=' + this.getBoundary()
  189   };
  190 
  191   for (var header in userHeaders) {
  192     formHeaders[header.toLowerCase()] = userHeaders[header];
  193   }
  194 
  195   return formHeaders;
  196 }
  197 
  198 FormData.prototype.getCustomHeaders = function(contentType) {
  199     contentType = contentType ? contentType : 'multipart/form-data';
  200 
  201     var formHeaders = {
  202         'content-type': contentType + '; boundary=' + this.getBoundary(),
  203         'content-length': this.getLengthSync()
  204     };
  205 
  206     return formHeaders;
  207 }
  208 
  209 FormData.prototype.getBoundary = function() {
  210   if (!this._boundary) {
  211     this._generateBoundary();
  212   }
  213 
  214   return this._boundary;
  215 };
  216 
  217 FormData.prototype._generateBoundary = function() {
  218   // This generates a 50 character boundary similar to those used by Firefox.
  219   // They are optimized for boyer-moore parsing.
  220   var boundary = '--------------------------';
  221   for (var i = 0; i < 24; i++) {
  222     boundary += Math.floor(Math.random() * 10).toString(16);
  223   }
  224 
  225   this._boundary = boundary;
  226 };
  227 
  228 // Note: getLengthSync DOESN'T calculate streams length
  229 // As workaround one can calculate file size manually
  230 // and add it as knownLength option
  231 FormData.prototype.getLengthSync = function(debug) {
  232   var knownLength = this._overheadLength + this._valueLength;
  233 
  234   // Don't get confused, there are 3 "internal" streams for each keyval pair
  235   // so it basically checks if there is any value added to the form
  236   if (this._streams.length) {
  237     knownLength += this._lastBoundary().length;
  238   }
  239 
  240   // https://github.com/felixge/node-form-data/issues/40
  241   if (this._lengthRetrievers.length) {
  242     // Some async length retrivers are present
  243     // therefore synchronous length calculation is false.
  244     // Please use getLength(callback) to get proper length
  245     this._error(new Error('Cannot calculate proper length in synchronous way.'));
  246   }
  247 
  248   return knownLength;
  249 };
  250 
  251 FormData.prototype.getLength = function(cb) {
  252   var knownLength = this._overheadLength + this._valueLength;
  253 
  254   if (this._streams.length) {
  255     knownLength += this._lastBoundary().length;
  256   }
  257 
  258   if (!this._lengthRetrievers.length) {
  259     process.nextTick(cb.bind(this, null, knownLength));
  260     return;
  261   }
  262 
  263   async.parallel(this._lengthRetrievers, function(err, values) {
  264     if (err) {
  265       cb(err);
  266       return;
  267     }
  268 
  269     values.forEach(function(length) {
  270       knownLength += length;
  271     });
  272 
  273     cb(null, knownLength);
  274   });
  275 };
  276 
  277 FormData.prototype.submit = function(params, cb) {
  278 
  279   var request
  280     , options
  281     , defaults = {
  282         method : 'post'
  283     };
  284 
  285   // parse provided url if it's string
  286   // or treat it as options object
  287   if (typeof params == 'string') {
  288     params = parseUrl(params);
  289 
  290     options = populate({
  291       port: params.port,
  292       path: params.pathname,
  293       host: params.hostname
  294     }, defaults);
  295   }
  296   else // use custom params
  297   {
  298     options = populate(params, defaults);
  299     // if no port provided use default one
  300     if (!options.port) {
  301       options.port = options.protocol == 'https:' ? 443 : 80;
  302     }
  303   }
  304 
  305   // put that good code in getHeaders to some use
  306   options.headers = this.getHeaders(params.headers);
  307 
  308   // https if specified, fallback to http in any other case
  309   if (params.protocol == 'https:') {
  310     request = https.request(options);
  311   } else {
  312     request = http.request(options);
  313   }
  314 
  315   // get content length and fire away
  316   this.getLength(function(err, length) {
  317 
  318     // TODO: Add chunked encoding when no length (if err)
  319 
  320     // add content length
  321     request.setHeader('Content-Length', length);
  322 
  323     this.pipe(request);
  324     if (cb) {
  325       request.on('error', cb);
  326       request.on('response', cb.bind(this, null));
  327     }
  328   }.bind(this));
  329 
  330   return request;
  331 };
  332 
  333 FormData.prototype._error = function(err) {
  334   if (this.error) return;
  335 
  336   this.error = err;
  337   this.pause();
  338   this.emit('error', err);
  339 };
  340 
  341 /*
  342  * Santa's little helpers
  343  */
  344 
  345 // populates missing values
  346 function populate(dst, src) {
  347   for (var prop in src) {
  348     if (!dst[prop]) dst[prop] = src[prop];
  349   }
  350   return dst;
  351 }