Remove CoffeeScript, Gulp, Lodash; add ESLint, Babel
parent
dd56e50ad6
commit
7f386ae5b8
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[ "@babel/preset-env" ]
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
node 0.10
|
@ -1,28 +0,0 @@
|
|||||||
var gulp = require('gulp');
|
|
||||||
|
|
||||||
/* CoffeeScript compile deps */
|
|
||||||
var path = require('path');
|
|
||||||
var gutil = require('gulp-util');
|
|
||||||
var concat = require('gulp-concat');
|
|
||||||
var rename = require('gulp-rename');
|
|
||||||
var coffee = require('gulp-coffee');
|
|
||||||
var cache = require('gulp-cached');
|
|
||||||
var remember = require('gulp-remember');
|
|
||||||
var plumber = require('gulp-plumber');
|
|
||||||
|
|
||||||
var source = ["lib/**/*.coffee", "index.coffee"]
|
|
||||||
|
|
||||||
gulp.task('coffee', function() {
|
|
||||||
return gulp.src(source, {base: "."})
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(cache("coffee"))
|
|
||||||
.pipe(coffee({bare: true}).on('error', gutil.log)).on('data', gutil.log)
|
|
||||||
.pipe(remember("coffee"))
|
|
||||||
.pipe(gulp.dest("."));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('watch', function () {
|
|
||||||
gulp.watch(source, ['coffee']);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('default', ['coffee', 'watch']);
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require "./lib/form-data2"
|
|
@ -1 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
module.exports = require("./lib/form-data2");
|
module.exports = require("./lib/form-data2");
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
# Standard library
|
|
||||||
path = require "path"
|
|
||||||
stream = require "stream"
|
|
||||||
|
|
||||||
# Third-party dependencies
|
|
||||||
uuid = require "uuid"
|
|
||||||
mime = require "mime"
|
|
||||||
combinedStream2 = require "combined-stream2"
|
|
||||||
Promise = require "bluebird"
|
|
||||||
_ = require "lodash"
|
|
||||||
debug = require("debug")("form-data2")
|
|
||||||
|
|
||||||
CRLF = "\r\n"
|
|
||||||
|
|
||||||
# Utility functions
|
|
||||||
ofTypes = (obj, types) ->
|
|
||||||
match = false
|
|
||||||
for type in types
|
|
||||||
match = match or obj instanceof type
|
|
||||||
return match
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = class FormData
|
|
||||||
constructor: ->
|
|
||||||
@_firstHeader = false
|
|
||||||
@_closingHeaderAppended = false
|
|
||||||
@_boundary = "----" + uuid.v4()
|
|
||||||
@_headers = { "content-type": "multipart/form-data; boundary=#{@_boundary}" }
|
|
||||||
@_stream = combinedStream2.create()
|
|
||||||
|
|
||||||
_getStreamMetadata: (source, options) -> # FIXME: Make work with deferred sources (ie. callback-provided)
|
|
||||||
debug "obtaining metadata for source: %s", source.toString().replace(/\n/g, "\\n").replace(/\r/g, "\\r")
|
|
||||||
fullPath = options.filename ? source.client?._httpMessage?.path ? source.path
|
|
||||||
|
|
||||||
if fullPath? # This is a file...
|
|
||||||
filename = path.basename(fullPath)
|
|
||||||
contentType = options.contentType ? source.headers?["content-type"] ? mime.lookup(filename)
|
|
||||||
contentLength = options.knownLength ? options.contentLength ? source.headers?["content-length"] # FIXME: Is this even used anywhere?
|
|
||||||
else # Probably just a plaintext form value, or an unidentified stream
|
|
||||||
contentType = options.contentType ? source.headers?["content-type"]
|
|
||||||
contentLength = options.knownLength ? options.contentLength ? source.headers?["content-length"]
|
|
||||||
|
|
||||||
return {filename: filename, contentType: contentType, contentLength: contentLength}
|
|
||||||
|
|
||||||
_generateHeaderFields: (name, metadata) ->
|
|
||||||
debug "generating headers for: %s", metadata
|
|
||||||
headerFields = []
|
|
||||||
|
|
||||||
if metadata.filename?
|
|
||||||
escapedFilename = metadata.filename.replace '"', '\\"'
|
|
||||||
headerFields.push "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{escapedFilename}\""
|
|
||||||
else
|
|
||||||
headerFields.push "Content-Disposition: form-data; name=\"#{name}\""
|
|
||||||
|
|
||||||
if metadata.contentType?
|
|
||||||
headerFields.push "Content-Type: #{metadata.contentType}"
|
|
||||||
|
|
||||||
debug "generated headers: %s", headerFields
|
|
||||||
return headerFields.join CRLF
|
|
||||||
|
|
||||||
_appendHeader: (name, metadata) ->
|
|
||||||
if @_firstHeader == false
|
|
||||||
debug "appending header"
|
|
||||||
leadingCRLF = ""
|
|
||||||
@_firstHeader = true
|
|
||||||
else
|
|
||||||
debug "appending first header"
|
|
||||||
leadingCRLF = CRLF
|
|
||||||
|
|
||||||
headerFields = @_generateHeaderFields name, metadata
|
|
||||||
|
|
||||||
@_stream.append new Buffer(leadingCRLF + "--#{@_boundary}" + CRLF + headerFields + CRLF + CRLF)
|
|
||||||
|
|
||||||
_appendClosingHeader: ->
|
|
||||||
debug "appending closing header"
|
|
||||||
@_stream.append new Buffer(CRLF + "--#{@_boundary}--")
|
|
||||||
|
|
||||||
append: (name, source, options = {}) ->
|
|
||||||
debug "appending source"
|
|
||||||
if @_closingHeaderAppended
|
|
||||||
throw new Error "The stream has already been prepared for usage; you either piped it or generated the HTTP headers. No new sources can be appended anymore."
|
|
||||||
|
|
||||||
if not ofTypes(source, [stream.Readable, stream.Duplex, stream.Transform, Buffer, Function]) and typeof source != "string"
|
|
||||||
throw new Error "The provided value must be either a readable stream, a Buffer, a callback providing either of those, or a string."
|
|
||||||
|
|
||||||
if typeof source == "string"
|
|
||||||
source = new Buffer(source) # If the string isn't UTF-8, this won't end well!
|
|
||||||
options.contentType ?= "text/plain"
|
|
||||||
|
|
||||||
metadata = @_getStreamMetadata source, options
|
|
||||||
@_appendHeader name, metadata
|
|
||||||
|
|
||||||
@_stream.append source, options
|
|
||||||
|
|
||||||
done: ->
|
|
||||||
# This method should be called when the user is finished adding streams. It adds the termination header at the end of the combined stream. When piping, this method is automatically called!
|
|
||||||
debug "called 'done'"
|
|
||||||
|
|
||||||
if not @_closingHeaderAppended
|
|
||||||
@_closingHeaderAppended = true
|
|
||||||
@_appendClosingHeader()
|
|
||||||
|
|
||||||
getBoundary: ->
|
|
||||||
return @_boundary
|
|
||||||
|
|
||||||
getHeaders: (callback) ->
|
|
||||||
# Returns the headers needed to correctly transmit the generated multipart/form-data blob. We will first need to call @done() to make sure that the multipart footer is there - from this point on, no new sources can be appended anymore.
|
|
||||||
@done()
|
|
||||||
|
|
||||||
Promise.try =>
|
|
||||||
@_stream.getCombinedStreamLength()
|
|
||||||
.then (length) ->
|
|
||||||
debug "total combined stream length: %s", length
|
|
||||||
Promise.resolve { "content-length": length }
|
|
||||||
.catch (err) ->
|
|
||||||
# We couldn't get the stream length, most likely there was a stream involved that `stream-length` does not support.
|
|
||||||
debug "WARN: could not get total combined stream length"
|
|
||||||
Promise.resolve { "transfer-encoding": "chunked" }
|
|
||||||
.then (sizeHeaders) =>
|
|
||||||
Promise.resolve _.extend(sizeHeaders, @_headers)
|
|
||||||
.nodeify(callback)
|
|
||||||
|
|
||||||
getLength: (callback) ->
|
|
||||||
@_stream.getCombinedStreamLength(callback)
|
|
||||||
|
|
||||||
pipe: (target) ->
|
|
||||||
@done()
|
|
||||||
|
|
||||||
# Pass through to the underlying `combined-stream`.
|
|
||||||
debug "piping underlying combined-stream2 to target writable"
|
|
||||||
@_stream.pipe target
|
|
@ -1,167 +1,260 @@
|
|||||||
var CRLF, FormData, Promise, combinedStream2, debug, mime, ofTypes, path, stream, uuid, _;
|
"use strict"; // Standard library
|
||||||
|
|
||||||
path = require("path");
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
stream = require("stream");
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
||||||
|
|
||||||
uuid = require("uuid");
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
||||||
|
|
||||||
mime = require("mime");
|
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
|
||||||
|
|
||||||
combinedStream2 = require("combined-stream2");
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
|
||||||
|
|
||||||
Promise = require("bluebird");
|
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
|
||||||
|
|
||||||
_ = require("lodash");
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
|
||||||
|
|
||||||
debug = require("debug")("form-data2");
|
var path = require("path");
|
||||||
|
|
||||||
CRLF = "\r\n";
|
var stream = require("stream"); // Third-party dependencies
|
||||||
|
|
||||||
ofTypes = function(obj, types) {
|
|
||||||
var match, type, _i, _len;
|
var uuid = require("uuid");
|
||||||
match = false;
|
|
||||||
for (_i = 0, _len = types.length; _i < _len; _i++) {
|
var mime = require("mime");
|
||||||
type = types[_i];
|
|
||||||
match = match || obj instanceof type;
|
var combinedStream2 = require("combined-stream2");
|
||||||
|
|
||||||
|
var Promise = require("bluebird");
|
||||||
|
|
||||||
|
var debug = require("debug")("form-data2");
|
||||||
|
|
||||||
|
var CRLF = "\r\n"; // Utility functions
|
||||||
|
|
||||||
|
var ofTypes = function ofTypes(obj, types) {
|
||||||
|
var match = false;
|
||||||
|
var _iteratorNormalCompletion = true;
|
||||||
|
var _didIteratorError = false;
|
||||||
|
var _iteratorError = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator = types[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||||
|
var type = _step.value;
|
||||||
|
match = match || obj instanceof type;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError = true;
|
||||||
|
_iteratorError = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
||||||
|
_iterator.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError) {
|
||||||
|
throw _iteratorError;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return match;
|
return match;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = FormData = (function() {
|
function assign() {
|
||||||
|
for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||||
|
objects[_key] = arguments[_key];
|
||||||
|
}
|
||||||
|
|
||||||
|
var validObjects = objects.filter(function (object) {
|
||||||
|
return object != null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validObjects.length === 0) {
|
||||||
|
return {};
|
||||||
|
} else if (validObjects.length === 1) {
|
||||||
|
return validObjects[0];
|
||||||
|
} else {
|
||||||
|
return Object.assign.apply(Object, _toConsumableArray(validObjects));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
/*#__PURE__*/
|
||||||
|
function () {
|
||||||
function FormData() {
|
function FormData() {
|
||||||
|
_classCallCheck(this, FormData);
|
||||||
|
|
||||||
this._firstHeader = false;
|
this._firstHeader = false;
|
||||||
this._closingHeaderAppended = false;
|
this._closingHeaderAppended = false;
|
||||||
this._boundary = "----" + uuid.v4();
|
this._boundary = "----" + uuid.v4();
|
||||||
this._headers = {
|
this._headers = {
|
||||||
"content-type": "multipart/form-data; boundary=" + this._boundary
|
"content-type": "multipart/form-data; boundary=".concat(this._boundary)
|
||||||
};
|
};
|
||||||
this._stream = combinedStream2.create();
|
this._stream = combinedStream2.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
FormData.prototype._getStreamMetadata = function(source, options) {
|
_createClass(FormData, [{
|
||||||
var contentLength, contentType, filename, fullPath, _ref, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9;
|
key: "_getStreamMetadata",
|
||||||
debug("obtaining metadata for source: %s", source.toString().replace(/\n/g, "\\n").replace(/\r/g, "\\r"));
|
value: function _getStreamMetadata(source, options) {
|
||||||
fullPath = (_ref = (_ref1 = options.filename) != null ? _ref1 : (_ref2 = source.client) != null ? (_ref3 = _ref2._httpMessage) != null ? _ref3.path : void 0 : void 0) != null ? _ref : source.path;
|
// FIXME: Make work with deferred sources (ie. callback-provided)
|
||||||
if (fullPath != null) {
|
var contentLength, contentType, filename, left;
|
||||||
filename = path.basename(fullPath);
|
debug("obtaining metadata for source: %s", source.toString().replace(/\n/g, "\\n").replace(/\r/g, "\\r"));
|
||||||
contentType = (_ref4 = (_ref5 = options.contentType) != null ? _ref5 : (_ref6 = source.headers) != null ? _ref6["content-type"] : void 0) != null ? _ref4 : mime.lookup(filename);
|
var fullPath = (left = options.filename != null ? options.filename : __guard__(source.client != null ? source.client._httpMessage : undefined, function (x) {
|
||||||
contentLength = (_ref7 = (_ref8 = options.knownLength) != null ? _ref8 : options.contentLength) != null ? _ref7 : (_ref9 = source.headers) != null ? _ref9["content-length"] : void 0;
|
return x.path;
|
||||||
} else {
|
})) != null ? left : source.path;
|
||||||
contentType = (_ref10 = options.contentType) != null ? _ref10 : (_ref11 = source.headers) != null ? _ref11["content-type"] : void 0;
|
|
||||||
contentLength = (_ref12 = (_ref13 = options.knownLength) != null ? _ref13 : options.contentLength) != null ? _ref12 : (_ref14 = source.headers) != null ? _ref14["content-length"] : void 0;
|
if (fullPath != null) {
|
||||||
}
|
// This is a file...
|
||||||
return {
|
var left1, left2;
|
||||||
filename: filename,
|
filename = path.basename(fullPath);
|
||||||
contentType: contentType,
|
contentType = (left1 = options.contentType != null ? options.contentType : source.headers != null ? source.headers["content-type"] : undefined) != null ? left1 : mime.lookup(filename);
|
||||||
contentLength: contentLength
|
contentLength = (left2 = options.knownLength != null ? options.knownLength : options.contentLength) != null ? left2 : source.headers != null ? source.headers["content-length"] : undefined; // FIXME: Is this even used anywhere?
|
||||||
};
|
} else {
|
||||||
};
|
// Probably just a plaintext form value, or an unidentified stream
|
||||||
|
var left3;
|
||||||
FormData.prototype._generateHeaderFields = function(name, metadata) {
|
contentType = options.contentType != null ? options.contentType : source.headers != null ? source.headers["content-type"] : undefined;
|
||||||
var escapedFilename, headerFields;
|
contentLength = (left3 = options.knownLength != null ? options.knownLength : options.contentLength) != null ? left3 : source.headers != null ? source.headers["content-length"] : undefined;
|
||||||
debug("generating headers for: %s", metadata);
|
}
|
||||||
headerFields = [];
|
|
||||||
if (metadata.filename != null) {
|
return {
|
||||||
escapedFilename = metadata.filename.replace('"', '\\"');
|
filename: filename,
|
||||||
headerFields.push("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + escapedFilename + "\"");
|
contentType: contentType,
|
||||||
} else {
|
contentLength: contentLength
|
||||||
headerFields.push("Content-Disposition: form-data; name=\"" + name + "\"");
|
};
|
||||||
}
|
|
||||||
if (metadata.contentType != null) {
|
|
||||||
headerFields.push("Content-Type: " + metadata.contentType);
|
|
||||||
}
|
}
|
||||||
debug("generated headers: %s", headerFields);
|
}, {
|
||||||
return headerFields.join(CRLF);
|
key: "_generateHeaderFields",
|
||||||
};
|
value: function _generateHeaderFields(name, metadata) {
|
||||||
|
debug("generating headers for: %s", metadata);
|
||||||
FormData.prototype._appendHeader = function(name, metadata) {
|
var headerFields = [];
|
||||||
var headerFields, leadingCRLF;
|
|
||||||
if (this._firstHeader === false) {
|
if (metadata.filename != null) {
|
||||||
debug("appending header");
|
var escapedFilename = metadata.filename.replace('"', '\\"');
|
||||||
leadingCRLF = "";
|
headerFields.push("Content-Disposition: form-data; name=\"".concat(name, "\"; filename=\"").concat(escapedFilename, "\""));
|
||||||
this._firstHeader = true;
|
} else {
|
||||||
} else {
|
headerFields.push("Content-Disposition: form-data; name=\"".concat(name, "\""));
|
||||||
debug("appending first header");
|
}
|
||||||
leadingCRLF = CRLF;
|
|
||||||
|
if (metadata.contentType != null) {
|
||||||
|
headerFields.push("Content-Type: ".concat(metadata.contentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("generated headers: %s", headerFields);
|
||||||
|
return headerFields.join(CRLF);
|
||||||
}
|
}
|
||||||
headerFields = this._generateHeaderFields(name, metadata);
|
}, {
|
||||||
return this._stream.append(new Buffer(leadingCRLF + ("--" + this._boundary) + CRLF + headerFields + CRLF + CRLF));
|
key: "_appendHeader",
|
||||||
};
|
value: function _appendHeader(name, metadata) {
|
||||||
|
var leadingCRLF;
|
||||||
FormData.prototype._appendClosingHeader = function() {
|
|
||||||
debug("appending closing header");
|
if (this._firstHeader === false) {
|
||||||
return this._stream.append(new Buffer(CRLF + ("--" + this._boundary + "--")));
|
debug("appending header");
|
||||||
};
|
leadingCRLF = "";
|
||||||
|
this._firstHeader = true;
|
||||||
FormData.prototype.append = function(name, source, options) {
|
} else {
|
||||||
var metadata;
|
debug("appending first header");
|
||||||
if (options == null) {
|
leadingCRLF = CRLF;
|
||||||
options = {};
|
}
|
||||||
|
|
||||||
|
var headerFields = this._generateHeaderFields(name, metadata);
|
||||||
|
|
||||||
|
return this._stream.append(new Buffer(leadingCRLF + "--".concat(this._boundary) + CRLF + headerFields + CRLF + CRLF));
|
||||||
}
|
}
|
||||||
debug("appending source");
|
}, {
|
||||||
if (this._closingHeaderAppended) {
|
key: "_appendClosingHeader",
|
||||||
throw new Error("The stream has already been prepared for usage; you either piped it or generated the HTTP headers. No new sources can be appended anymore.");
|
value: function _appendClosingHeader() {
|
||||||
|
debug("appending closing header");
|
||||||
|
return this._stream.append(new Buffer(CRLF + "--".concat(this._boundary, "--")));
|
||||||
}
|
}
|
||||||
if (!ofTypes(source, [stream.Readable, stream.Duplex, stream.Transform, Buffer, Function]) && typeof source !== "string") {
|
}, {
|
||||||
throw new Error("The provided value must be either a readable stream, a Buffer, a callback providing either of those, or a string.");
|
key: "append",
|
||||||
|
value: function append(name, source, options) {
|
||||||
|
if (options == null) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("appending source");
|
||||||
|
|
||||||
|
if (this._closingHeaderAppended) {
|
||||||
|
throw new Error("The stream has already been prepared for usage; you either piped it or generated the HTTP headers. No new sources can be appended anymore.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ofTypes(source, [stream.Readable, stream.Duplex, stream.Transform, Buffer, Function]) && typeof source !== "string") {
|
||||||
|
throw new Error("The provided value must be either a readable stream, a Buffer, a callback providing either of those, or a string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof source === "string") {
|
||||||
|
source = new Buffer(source); // If the string isn't UTF-8, this won't end well!
|
||||||
|
|
||||||
|
if (options.contentType == null) {
|
||||||
|
options.contentType = "text/plain";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = this._getStreamMetadata(source, options);
|
||||||
|
|
||||||
|
this._appendHeader(name, metadata);
|
||||||
|
|
||||||
|
return this._stream.append(source, options);
|
||||||
}
|
}
|
||||||
if (typeof source === "string") {
|
}, {
|
||||||
source = new Buffer(source);
|
key: "done",
|
||||||
if (options.contentType == null) {
|
value: function done() {
|
||||||
options.contentType = "text/plain";
|
// This method should be called when the user is finished adding streams. It adds the termination header at the end of the combined stream. When piping, this method is automatically called!
|
||||||
|
debug("called 'done'");
|
||||||
|
|
||||||
|
if (!this._closingHeaderAppended) {
|
||||||
|
this._closingHeaderAppended = true;
|
||||||
|
return this._appendClosingHeader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata = this._getStreamMetadata(source, options);
|
}, {
|
||||||
this._appendHeader(name, metadata);
|
key: "getBoundary",
|
||||||
return this._stream.append(source, options);
|
value: function getBoundary() {
|
||||||
};
|
return this._boundary;
|
||||||
|
|
||||||
FormData.prototype.done = function() {
|
|
||||||
debug("called 'done'");
|
|
||||||
if (!this._closingHeaderAppended) {
|
|
||||||
this._closingHeaderAppended = true;
|
|
||||||
return this._appendClosingHeader();
|
|
||||||
}
|
}
|
||||||
};
|
}, {
|
||||||
|
key: "getHeaders",
|
||||||
FormData.prototype.getBoundary = function() {
|
value: function getHeaders(callback) {
|
||||||
return this._boundary;
|
var _this = this;
|
||||||
};
|
|
||||||
|
|
||||||
FormData.prototype.getHeaders = function(callback) {
|
// Returns the headers needed to correctly transmit the generated multipart/form-data blob. We will first need to call @done() to make sure that the multipart footer is there - from this point on, no new sources can be appended anymore.
|
||||||
this.done();
|
this.done();
|
||||||
return Promise["try"]((function(_this) {
|
return Promise.try(function () {
|
||||||
return function() {
|
|
||||||
return _this._stream.getCombinedStreamLength();
|
return _this._stream.getCombinedStreamLength();
|
||||||
};
|
}).then(function (length) {
|
||||||
})(this)).then(function(length) {
|
debug("total combined stream length: %s", length);
|
||||||
debug("total combined stream length: %s", length);
|
return Promise.resolve({
|
||||||
return Promise.resolve({
|
"content-length": length
|
||||||
"content-length": length
|
});
|
||||||
});
|
}).catch(function (_error) {
|
||||||
})["catch"](function(err) {
|
// We couldn't get the stream length, most likely there was a stream involved that `stream-length` does not support.
|
||||||
debug("WARN: could not get total combined stream length");
|
debug("WARN: could not get total combined stream length");
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
"transfer-encoding": "chunked"
|
"transfer-encoding": "chunked"
|
||||||
});
|
});
|
||||||
}).then((function(_this) {
|
}).then(function (sizeHeaders) {
|
||||||
return function(sizeHeaders) {
|
return Promise.resolve(assign(sizeHeaders, _this._headers));
|
||||||
return Promise.resolve(_.extend(sizeHeaders, _this._headers));
|
}).nodeify(callback);
|
||||||
};
|
}
|
||||||
})(this)).nodeify(callback);
|
}, {
|
||||||
};
|
key: "getLength",
|
||||||
|
value: function getLength(callback) {
|
||||||
FormData.prototype.getLength = function(callback) {
|
return this._stream.getCombinedStreamLength(callback);
|
||||||
return this._stream.getCombinedStreamLength(callback);
|
}
|
||||||
};
|
}, {
|
||||||
|
key: "pipe",
|
||||||
|
value: function pipe(target) {
|
||||||
|
this.done(); // Pass through to the underlying `combined-stream`.
|
||||||
|
|
||||||
FormData.prototype.pipe = function(target) {
|
debug("piping underlying combined-stream2 to target writable");
|
||||||
this.done();
|
return this._stream.pipe(target);
|
||||||
debug("piping underlying combined-stream2 to target writable");
|
}
|
||||||
return this._stream.pipe(target);
|
}]);
|
||||||
};
|
|
||||||
|
|
||||||
return FormData;
|
return FormData;
|
||||||
|
}();
|
||||||
|
|
||||||
})();
|
function __guard__(value, transform) {
|
||||||
|
return typeof value !== 'undefined' && value !== null ? transform(value) : undefined;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Standard library
|
||||||
|
const path = require("path");
|
||||||
|
const stream = require("stream");
|
||||||
|
|
||||||
|
// Third-party dependencies
|
||||||
|
const uuid = require("uuid");
|
||||||
|
const mime = require("mime");
|
||||||
|
const combinedStream2 = require("combined-stream2");
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const debug = require("debug")("form-data2");
|
||||||
|
|
||||||
|
const CRLF = "\r\n";
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
const ofTypes = function(obj, types) {
|
||||||
|
let match = false;
|
||||||
|
for (let type of types) {
|
||||||
|
match = match || obj instanceof type;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
};
|
||||||
|
|
||||||
|
function assign(...objects) {
|
||||||
|
let validObjects = objects.filter((object) => object != null);
|
||||||
|
|
||||||
|
if (validObjects.length === 0) {
|
||||||
|
return {};
|
||||||
|
} else if (validObjects.length === 1) {
|
||||||
|
return validObjects[0];
|
||||||
|
} else {
|
||||||
|
return Object.assign(...validObjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = class FormData {
|
||||||
|
constructor() {
|
||||||
|
this._firstHeader = false;
|
||||||
|
this._closingHeaderAppended = false;
|
||||||
|
this._boundary = "----" + uuid.v4();
|
||||||
|
this._headers = { "content-type": `multipart/form-data; boundary=${this._boundary}` };
|
||||||
|
this._stream = combinedStream2.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getStreamMetadata(source, options) { // FIXME: Make work with deferred sources (ie. callback-provided)
|
||||||
|
let contentLength, contentType, filename, left;
|
||||||
|
debug("obtaining metadata for source: %s", source.toString().replace(/\n/g, "\\n").replace(/\r/g, "\\r"));
|
||||||
|
const fullPath = (left = options.filename != null ? options.filename : __guard__(source.client != null ? source.client._httpMessage : undefined, x => x.path)) != null ? left : source.path;
|
||||||
|
|
||||||
|
if (fullPath != null) { // This is a file...
|
||||||
|
let left1, left2;
|
||||||
|
filename = path.basename(fullPath);
|
||||||
|
contentType = (left1 = options.contentType != null ? options.contentType : (source.headers != null ? source.headers["content-type"] : undefined)) != null ? left1 : mime.lookup(filename);
|
||||||
|
contentLength = (left2 = options.knownLength != null ? options.knownLength : options.contentLength) != null ? left2 : (source.headers != null ? source.headers["content-length"] : undefined); // FIXME: Is this even used anywhere?
|
||||||
|
} else { // Probably just a plaintext form value, or an unidentified stream
|
||||||
|
let left3;
|
||||||
|
contentType = options.contentType != null ? options.contentType : (source.headers != null ? source.headers["content-type"] : undefined);
|
||||||
|
contentLength = (left3 = options.knownLength != null ? options.knownLength : options.contentLength) != null ? left3 : (source.headers != null ? source.headers["content-length"] : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {filename, contentType, contentLength};
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateHeaderFields(name, metadata) {
|
||||||
|
debug("generating headers for: %s", metadata);
|
||||||
|
const headerFields = [];
|
||||||
|
|
||||||
|
if (metadata.filename != null) {
|
||||||
|
const escapedFilename = metadata.filename.replace('"', '\\"');
|
||||||
|
headerFields.push(`Content-Disposition: form-data; name=\"${name}\"; filename=\"${escapedFilename}\"`);
|
||||||
|
} else {
|
||||||
|
headerFields.push(`Content-Disposition: form-data; name=\"${name}\"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata.contentType != null) {
|
||||||
|
headerFields.push(`Content-Type: ${metadata.contentType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("generated headers: %s", headerFields);
|
||||||
|
return headerFields.join(CRLF);
|
||||||
|
}
|
||||||
|
|
||||||
|
_appendHeader(name, metadata) {
|
||||||
|
let leadingCRLF;
|
||||||
|
if (this._firstHeader === false) {
|
||||||
|
debug("appending header");
|
||||||
|
leadingCRLF = "";
|
||||||
|
this._firstHeader = true;
|
||||||
|
} else {
|
||||||
|
debug("appending first header");
|
||||||
|
leadingCRLF = CRLF;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerFields = this._generateHeaderFields(name, metadata);
|
||||||
|
|
||||||
|
return this._stream.append(new Buffer(leadingCRLF + `--${this._boundary}` + CRLF + headerFields + CRLF + CRLF));
|
||||||
|
}
|
||||||
|
|
||||||
|
_appendClosingHeader() {
|
||||||
|
debug("appending closing header");
|
||||||
|
return this._stream.append(new Buffer(CRLF + `--${this._boundary}--`));
|
||||||
|
}
|
||||||
|
|
||||||
|
append(name, source, options) {
|
||||||
|
if (options == null) { options = {}; }
|
||||||
|
debug("appending source");
|
||||||
|
if (this._closingHeaderAppended) {
|
||||||
|
throw new Error("The stream has already been prepared for usage; you either piped it or generated the HTTP headers. No new sources can be appended anymore.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ofTypes(source, [stream.Readable, stream.Duplex, stream.Transform, Buffer, Function]) && (typeof source !== "string")) {
|
||||||
|
throw new Error("The provided value must be either a readable stream, a Buffer, a callback providing either of those, or a string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof source === "string") {
|
||||||
|
source = new Buffer(source); // If the string isn't UTF-8, this won't end well!
|
||||||
|
if (options.contentType == null) { options.contentType = "text/plain"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = this._getStreamMetadata(source, options);
|
||||||
|
this._appendHeader(name, metadata);
|
||||||
|
|
||||||
|
return this._stream.append(source, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
done() {
|
||||||
|
// This method should be called when the user is finished adding streams. It adds the termination header at the end of the combined stream. When piping, this method is automatically called!
|
||||||
|
debug("called 'done'");
|
||||||
|
|
||||||
|
if (!this._closingHeaderAppended) {
|
||||||
|
this._closingHeaderAppended = true;
|
||||||
|
return this._appendClosingHeader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBoundary() {
|
||||||
|
return this._boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaders(callback) {
|
||||||
|
// Returns the headers needed to correctly transmit the generated multipart/form-data blob. We will first need to call @done() to make sure that the multipart footer is there - from this point on, no new sources can be appended anymore.
|
||||||
|
this.done();
|
||||||
|
|
||||||
|
return Promise.try(() => {
|
||||||
|
return this._stream.getCombinedStreamLength();
|
||||||
|
}).then(function(length) {
|
||||||
|
debug("total combined stream length: %s", length);
|
||||||
|
return Promise.resolve({ "content-length": length });
|
||||||
|
}).catch(function(_error) {
|
||||||
|
// We couldn't get the stream length, most likely there was a stream involved that `stream-length` does not support.
|
||||||
|
debug("WARN: could not get total combined stream length");
|
||||||
|
return Promise.resolve({ "transfer-encoding": "chunked" });
|
||||||
|
}).then(sizeHeaders => {
|
||||||
|
return Promise.resolve(assign(sizeHeaders, this._headers));
|
||||||
|
}).nodeify(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLength(callback) {
|
||||||
|
return this._stream.getCombinedStreamLength(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe(target) {
|
||||||
|
this.done();
|
||||||
|
|
||||||
|
// Pass through to the underlying `combined-stream`.
|
||||||
|
debug("piping underlying combined-stream2 to target writable");
|
||||||
|
return this._stream.pipe(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function __guard__(value, transform) {
|
||||||
|
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
|
||||||
|
}
|
Loading…
Reference in New Issue