UNPKG

@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
/*! * @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. */ "use strict"; /*! * 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;