react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / request / node_modules / form-data / lib / form_data.js
81146 viewsvar CombinedStream = require('combined-stream');1var util = require('util');2var path = require('path');3var http = require('http');4var https = require('https');5var parseUrl = require('url').parse;6var fs = require('fs');7var mime = require('mime-types');8var async = require('async');910module.exports = FormData;11function FormData() {12this._overheadLength = 0;13this._valueLength = 0;14this._lengthRetrievers = [];1516CombinedStream.call(this);17}18util.inherits(FormData, CombinedStream);1920FormData.LINE_BREAK = '\r\n';2122FormData.prototype.append = function(field, value, options) {23options = options || {};2425var append = CombinedStream.prototype.append.bind(this);2627// all that streamy business can't handle numbers28if (typeof value == 'number') value = ''+value;2930// https://github.com/felixge/node-form-data/issues/3831if (util.isArray(value)) {32// Please convert your array into string33// the way web server expects it34this._error(new Error('Arrays are not supported.'));35return;36}3738var header = this._multiPartHeader(field, value, options);39var footer = this._multiPartFooter(field, value, options);4041append(header);42append(value);43append(footer);4445// pass along options.knownLength46this._trackLength(header, value, options);47};4849FormData.prototype._trackLength = function(header, value, options) {50var valueLength = 0;5152// used w/ getLengthSync(), when length is known.53// e.g. for streaming directly from a remote server,54// w/ a known file a size, and not wanting to wait for55// incoming file to finish to get its size.56if (options.knownLength != null) {57valueLength += +options.knownLength;58} else if (Buffer.isBuffer(value)) {59valueLength = value.length;60} else if (typeof value === 'string') {61valueLength = Buffer.byteLength(value);62}6364this._valueLength += valueLength;6566// @check why add CRLF? does this account for custom/multiple CRLFs?67this._overheadLength +=68Buffer.byteLength(header) +69+ FormData.LINE_BREAK.length;7071// empty or either doesn't have path or not an http response72if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {73return;74}7576// no need to bother with the length77if (!options.knownLength)78this._lengthRetrievers.push(function(next) {7980if (value.hasOwnProperty('fd')) {8182// take read range into a account83// `end` = Infinity –> read file till the end84//85// TODO: Looks like there is bug in Node fs.createReadStream86// it doesn't respect `end` options without `start` options87// Fix it when node fixes it.88// https://github.com/joyent/node/issues/781989if (value.end != undefined && value.end != Infinity && value.start != undefined) {9091// when end specified92// no need to calculate range93// inclusive, starts with 094next(null, value.end+1 - (value.start ? value.start : 0));9596// not that fast snoopy97} else {98// still need to fetch file size from fs99fs.stat(value.path, function(err, stat) {100101var fileSize;102103if (err) {104next(err);105return;106}107108// update final size based on the range options109fileSize = stat.size - (value.start ? value.start : 0);110next(null, fileSize);111});112}113114// or http response115} else if (value.hasOwnProperty('httpVersion')) {116next(null, +value.headers['content-length']);117118// or request stream http://github.com/mikeal/request119} else if (value.hasOwnProperty('httpModule')) {120// wait till response come back121value.on('response', function(response) {122value.pause();123next(null, +response.headers['content-length']);124});125value.resume();126127// something else128} else {129next('Unknown stream');130}131});132};133134FormData.prototype._multiPartHeader = function(field, value, options) {135var boundary = this.getBoundary();136var header = '';137138// custom header specified (as string)?139// it becomes responsible for boundary140// (e.g. to handle extra CRLFs on .NET servers)141if (options.header != null) {142header = options.header;143} else {144header += '--' + boundary + FormData.LINE_BREAK +145'Content-Disposition: form-data; name="' + field + '"';146147// fs- and request- streams have path property148// or use custom filename and/or contentType149// TODO: Use request's response mime-type150if (options.filename || value.path) {151header +=152'; filename="' + path.basename(options.filename || value.path) + '"' + FormData.LINE_BREAK +153'Content-Type: ' + (options.contentType || mime.lookup(options.filename || value.path));154155// http response has not156} else if (value.readable && value.hasOwnProperty('httpVersion')) {157header +=158'; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +159'Content-Type: ' + value.headers['content-type'];160}161162header += FormData.LINE_BREAK + FormData.LINE_BREAK;163}164165return header;166};167168FormData.prototype._multiPartFooter = function(field, value, options) {169return function(next) {170var footer = FormData.LINE_BREAK;171172var lastPart = (this._streams.length === 0);173if (lastPart) {174footer += this._lastBoundary();175}176177next(footer);178}.bind(this);179};180181FormData.prototype._lastBoundary = function() {182return '--' + this.getBoundary() + '--';183};184185FormData.prototype.getHeaders = function(userHeaders) {186var formHeaders = {187'content-type': 'multipart/form-data; boundary=' + this.getBoundary()188};189190for (var header in userHeaders) {191formHeaders[header.toLowerCase()] = userHeaders[header];192}193194return formHeaders;195}196197FormData.prototype.getCustomHeaders = function(contentType) {198contentType = contentType ? contentType : 'multipart/form-data';199200var formHeaders = {201'content-type': contentType + '; boundary=' + this.getBoundary(),202'content-length': this.getLengthSync()203};204205return formHeaders;206}207208FormData.prototype.getBoundary = function() {209if (!this._boundary) {210this._generateBoundary();211}212213return this._boundary;214};215216FormData.prototype._generateBoundary = function() {217// This generates a 50 character boundary similar to those used by Firefox.218// They are optimized for boyer-moore parsing.219var boundary = '--------------------------';220for (var i = 0; i < 24; i++) {221boundary += Math.floor(Math.random() * 10).toString(16);222}223224this._boundary = boundary;225};226227// Note: getLengthSync DOESN'T calculate streams length228// As workaround one can calculate file size manually229// and add it as knownLength option230FormData.prototype.getLengthSync = function(debug) {231var knownLength = this._overheadLength + this._valueLength;232233// Don't get confused, there are 3 "internal" streams for each keyval pair234// so it basically checks if there is any value added to the form235if (this._streams.length) {236knownLength += this._lastBoundary().length;237}238239// https://github.com/felixge/node-form-data/issues/40240if (this._lengthRetrievers.length) {241// Some async length retrivers are present242// therefore synchronous length calculation is false.243// Please use getLength(callback) to get proper length244this._error(new Error('Cannot calculate proper length in synchronous way.'));245}246247return knownLength;248};249250FormData.prototype.getLength = function(cb) {251var knownLength = this._overheadLength + this._valueLength;252253if (this._streams.length) {254knownLength += this._lastBoundary().length;255}256257if (!this._lengthRetrievers.length) {258process.nextTick(cb.bind(this, null, knownLength));259return;260}261262async.parallel(this._lengthRetrievers, function(err, values) {263if (err) {264cb(err);265return;266}267268values.forEach(function(length) {269knownLength += length;270});271272cb(null, knownLength);273});274};275276FormData.prototype.submit = function(params, cb) {277278var request279, options280, defaults = {281method : 'post'282};283284// parse provided url if it's string285// or treat it as options object286if (typeof params == 'string') {287params = parseUrl(params);288289options = populate({290port: params.port,291path: params.pathname,292host: params.hostname293}, defaults);294}295else // use custom params296{297options = populate(params, defaults);298// if no port provided use default one299if (!options.port) {300options.port = options.protocol == 'https:' ? 443 : 80;301}302}303304// put that good code in getHeaders to some use305options.headers = this.getHeaders(params.headers);306307// https if specified, fallback to http in any other case308if (params.protocol == 'https:') {309request = https.request(options);310} else {311request = http.request(options);312}313314// get content length and fire away315this.getLength(function(err, length) {316317// TODO: Add chunked encoding when no length (if err)318319// add content length320request.setHeader('Content-Length', length);321322this.pipe(request);323if (cb) {324request.on('error', cb);325request.on('response', cb.bind(this, null));326}327}.bind(this));328329return request;330};331332FormData.prototype._error = function(err) {333if (this.error) return;334335this.error = err;336this.pause();337this.emit('error', err);338};339340/*341* Santa's little helpers342*/343344// populates missing values345function populate(dst, src) {346for (var prop in src) {347if (!dst[prop]) dst[prop] = src[prop];348}349return dst;350}351352353