@coolgk/utils
Version:
javascript, typescript utility and wrapper functions and classes: array, string, base64, ampq, bcrypt, cache, captcha, csv, email, jwt, number, pdf, tmp, token, unit conversion, url params, session, form data, google sign in, facebook sign in
198 lines (196 loc) • 8.39 kB
JavaScript
/*!
* @package @coolgk/utils
* @version 3.1.4
* @link https://github.com/coolgk/node-utils
* @license MIT
* @author Daniel Gong <daniel.k.gong@gmail.com>
*
* Copyright (c) 2017 Daniel Gong <daniel.k.gong@gmail.com>. All rights reserved.
* Licensed under the MIT License.
*/
;
/*!
* Copyright (c) 2017 Daniel Gong <daniel.k.gong@gmail.com>. All rights reserved.
* Licensed under the MIT License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const os_1 = require("os");
const Busboy = require("busboy");
const fs_1 = require("fs");
const tmp_1 = require("@coolgk/tmp");
const array_1 = require("@coolgk/array");
const querystring_1 = require("querystring");
var FormDataError;
(function (FormDataError) {
FormDataError["FILE_SIZE_EXCEEDED_LIMIT"] = "FILE_SIZE_EXCEEDED_LIMIT";
FormDataError["NUM_OF_NON_FILE_FIELDS_EXCEEDED_LIMIT"] = "NUM_OF_NON_FILE_FIELDS_EXCEEDED_LIMIT";
FormDataError["NUM_OF_FIELDS_EXCEEDED_LIMIT"] = "NUM_OF_FIELDS_EXCEEDED_LIMIT";
FormDataError["NUM_OF_FILES_EXCEEDED_LIMIT"] = "NUM_OF_FILES_EXCEEDED_LIMIT";
FormDataError["POST_SIZE_EXCEEDED_LIMIT"] = "POST_SIZE_EXCEEDED_LIMIT";
FormDataError["FIELD_SIZE_EXCEEDED_LIMIT"] = "FIELD_SIZE_EXCEEDED_LIMIT";
FormDataError["INVALID_JSON"] = "INVALID_JSON";
})(FormDataError = exports.FormDataError || (exports.FormDataError = {}));
function getFormData(request, options = {}) {
const parsers = {
'application/json': (data) => {
try {
return JSON.parse(data);
}
catch (error) {
throw FormDataError.INVALID_JSON;
}
},
'application/x-www-form-urlencoded': querystring_1.parse
};
const postSize = options.limits ? options.limits.postSize || 1024000 : 1024000;
const fieldsLimit = options.limits ? options.limits.fields || Infinity : Infinity;
const dir = options.dir || os_1.tmpdir();
const postfix = options.postfix || '';
const contentType = request.headers['content-type'] || '';
if (!contentType) {
return Promise.resolve({});
}
return new Promise((resolve, reject) => {
if (parsers[contentType]) {
const data = [];
let size = 0;
let overSize = false;
request.on('data', (chunk) => {
size += chunk.length;
if (size <= postSize) {
data.push(chunk);
}
else {
overSize = true;
}
});
request.on('error', (error) => reject(error));
request.on('end', () => {
if (overSize) {
reject(FormDataError.POST_SIZE_EXCEEDED_LIMIT);
}
else {
try {
const jsonData = parsers[contentType](Buffer.concat(data).toString());
if (Object.keys(jsonData).length > fieldsLimit) {
return reject(FormDataError.NUM_OF_FIELDS_EXCEEDED_LIMIT);
}
if (options.array) {
for (const property in jsonData) {
jsonData[property] = array_1.toArray(jsonData[property]);
}
}
resolve(jsonData);
}
catch (error) {
reject(error);
}
}
});
}
else if (contentType.indexOf('multipart/form-data') === 0) {
const busboy = new Busboy({ headers: request.headers, limits: options.limits });
const promises = [];
if (options.fileFieldNames) {
busboy.on('file', (fieldname, fileStream, filename, encoding, mimetype) => __awaiter(this, void 0, void 0, function* () {
if (array_1.toArray(options.fileFieldNames).includes(fieldname)) {
const { path, cleanupCallback } = yield tmp_1.generateFile({
dir,
postfix,
prefix: options.prefix,
mode: options.mode,
keep: true
});
const file = {
fieldname,
filename,
encoding,
mimetype,
size: 0,
path,
remove: cleanupCallback
};
fileStream.on('end', () => {
if (fileStream.truncated) {
file.error = FormDataError.FILE_SIZE_EXCEEDED_LIMIT;
}
});
promises.push(new Promise((done, rejectFile) => {
fileStream.pipe(fs_1.createWriteStream(path)).on('finish', () => {
fs_1.stat(path, (error, stats) => {
done(Object.assign({ error }, file, { size: stats.size }));
});
});
}));
}
else {
fileStream.resume();
}
}));
}
const data = {};
busboy.on('field', (fieldname, value, fieldnameTruncated, valueTruncated) => {
if (options.alwaysReject) {
if (valueTruncated) {
return reject(FormDataError.FIELD_SIZE_EXCEEDED_LIMIT);
}
}
if (!data[fieldname]) {
data[fieldname] = options.array ? [value] : value;
}
else {
data[fieldname] = array_1.toArray(data[fieldname]).concat(value);
}
});
busboy.on('partsLimit', () => reject(FormDataError.NUM_OF_FIELDS_EXCEEDED_LIMIT));
busboy.on('filesLimit', () => reject(FormDataError.NUM_OF_FILES_EXCEEDED_LIMIT));
busboy.on('fieldsLimit', () => reject(FormDataError.NUM_OF_NON_FILE_FIELDS_EXCEEDED_LIMIT));
busboy.on('error', (error) => {
reject(error);
});
busboy.on('finish', () => __awaiter(this, void 0, void 0, function* () {
(yield Promise.all(promises)).forEach((file) => {
if (file.error) {
file.remove();
}
if (!data[file.fieldname]) {
data[file.fieldname] = options.array ? [file] : file;
}
else {
data[file.fieldname] = array_1.toArray(data[file.fieldname]).concat(file);
}
});
resolve(data);
}));
request.pipe(busboy);
}
else {
resolve({});
}
});
}
exports.getFormData = getFormData;
function formData(request, globalOptions = {}) {
return {
getData: (fileFieldNames, localOptions = {}) => {
return getFormData(request, Object.assign({}, globalOptions, localOptions, { fileFieldNames }));
}
};
}
exports.formData = formData;
function express(options = {}) {
return (request, response, next) => {
request[options.requestFieldName || 'formdata'] = formData(request, options);
next();
};
}
exports.express = express;
exports.default = getFormData;