123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- 'use strict';
- const fs = require('fs');
- const path = require('path');
- const Readable = require('stream').Readable;
- // Parameters for safe file name parsing.
- const SAFE_FILE_NAME_REGEX = /[^\w-]/g;
- const MAX_EXTENSION_LENGTH = 3;
- // Parameters which used to generate unique temporary file names:
- const TEMP_COUNTER_MAX = 65536;
- const TEMP_PREFIX = 'tmp';
- let tempCounter = 0;
- /**
- * Logs message to console if debug option set to true.
- * @param {Object} options - options object.
- * @param {String} msg - message to log.
- * @returns {Boolean}
- */
- const debugLog = (options, msg) => {
- options = options || {};
- if (!options.debug) return false;
- console.log(msg); // eslint-disable-line
- return true;
- };
- /**
- * Generates unique temporary file name like: tmp-5000-156788789789.
- * @param prefix {String} - a prefix for generated unique file name.
- * @returns {String}
- */
- const getTempFilename = (prefix) => {
- prefix = prefix || TEMP_PREFIX;
- tempCounter ++;
- if (tempCounter > TEMP_COUNTER_MAX) tempCounter = 1;
- return `${prefix}-${tempCounter}-${Date.now()}`;
- };
- /**
- * Returns true if argument is a function.
- * @returns {Boolean}
- */
- const isFunc = func => func && func.constructor && func.call && func.apply ? true: false;
- /**
- * Set errorFunc to the same value as successFunc for callback mode.
- * @returns {Function}
- */
- const errorFunc = (resolve, reject) => isFunc(reject) ? reject : resolve;
- /**
- * Return a callback function for promise resole/reject args.
- * @returns {Function}
- */
- const promiseCallback = (resolve, reject) => {
- return (err) => {
- if (err) {
- errorFunc(resolve, reject)(err);
- } else {
- resolve();
- }
- };
- };
- /**
- * Builds instance options from arguments objects.
- * @returns {Object} - result options.
- */
- const buildOptions = function(){
- let result = {};
- [...arguments].forEach(options => {
- if (!options || typeof options !== 'object') return;
- Object.keys(options).forEach(key => result[key] = options[key]);
- });
- return result;
- };
- /**
- * Builds request fields (using to build req.body and req.files)
- * @param {Object} instance - request object.
- * @param {String} field - field name.
- * @param value - field value.
- * @returns {Object}
- */
- const buildFields = (instance, field, value) => {
- //Do nothing if value is not set.
- if (value === null || value === undefined){
- return instance;
- }
- instance = instance || {};
- // Non-array fields
- if (!instance[field]) {
- instance[field] = value;
- } else {
- // Array fields
- if (instance[field] instanceof Array){
- instance[field].push(value);
- } else {
- instance[field] = [instance[field], value];
- }
- }
- return instance;
- };
- /**
- * Creates a folder for file specified in the path variable
- * @param {Object} fileUploadOptions
- * @param {String} filePath
- */
- const checkAndMakeDir = function(fileUploadOptions, filePath){
- //Check upload options were set.
- if (!fileUploadOptions) return false;
- if (!fileUploadOptions.createParentPath) return false;
- //Check whether folder for the file exists.
- if (!filePath) return false;
- const parentPath = path.dirname(filePath);
- //Create folder if it is not exists.
- if (!fs.existsSync(parentPath)) fs.mkdirSync(parentPath);
- return true;
- };
- /**
- * Delete file.
- * @param {String} file - Path to the file to delete.
- */
- const deleteFile = (file, callback) => {
- fs.unlink(file, (err) => {
- if (err) {
- callback(err);
- return;
- }
- callback();
- });
- };
- /**
- * Copy file via streams
- * @param {String} src - Path to the source file
- * @param {String} dst - Path to the destination file.
- */
- const copyFile = function(src, dst, callback){
- //cbCalled flag and runCb helps to run cb only once.
- let cbCalled = false;
- let runCb = (err) => {
- if (cbCalled) return;
- cbCalled = true;
- callback(err);
- };
- //Create read stream
- let readable = fs.createReadStream(src);
- readable.on('error', runCb);
- //Create write stream
- let writable = fs.createWriteStream(dst);
- writable.on('error', (err)=>{
- readable.destroy();
- runCb(err);
- });
- writable.on('close', () => runCb());
- //Copy file via piping streams.
- readable.pipe(writable);
- };
- /**
- * Move file via streams by copieng and the deleteing src.
- * @param {String} src - Path to the source file
- * @param {String} dst - Path to the destination file.
- * @param {Function} callback - A callback function.
- */
- const moveFile = (src, dst, callback) => {
- // Copy file to dst.
- copyFile(src, dst, (err) => {
- if (err) {
- callback(err);
- return;
- }
- // Delete src file.
- deleteFile(src, callback);
- });
- };
- /**
- * Save buffer data to a file.
- * @param {Buffer} buffer - buffer to save to a file.
- * @param {String} filePath - path to a file.
- */
- const saveBufferToFile = function(buffer, filePath, callback){
- if (!Buffer.isBuffer(buffer)){
- callback(new Error('buffer variable should be a Buffer!'));
- return;
- }
- //Setup readable stream from buffer.
- let streamData = buffer;
- let readStream = Readable();
- readStream._read = ()=>{
- readStream.push(streamData);
- streamData = null;
- };
- //Setup file system writable stream.
- let fstream = fs.createWriteStream(filePath);
- fstream.on('error', error => callback(error));
- fstream.on('close', () => callback());
- //Copy file via piping streams.
- readStream.pipe(fstream);
- };
- /**
- * Parses filename and extension and returns object {name, extension}.
- * @param preserveExtension {Boolean, Integer} - true/false or number of characters for extension.
- * @param fileName {String} - file name to parse.
- * @returns {Object} - {name, extension}.
- */
- const parseFileNameExtension = (preserveExtension, fileName) => {
- let preserveExtensionLengh = parseInt(preserveExtension);
- let result = {name: fileName, extension: ''};
- if (!preserveExtension && preserveExtensionLengh !== 0){
- return result;
- }
- // Define maximum extension length
- let maxExtLength = isNaN(preserveExtensionLengh)
- ? MAX_EXTENSION_LENGTH
- : Math.abs(preserveExtensionLengh);
- let nameParts = fileName.split('.');
- if (nameParts.length < 2) {
- return result;
- }
-
- let extension = nameParts.pop();
- if (
- extension.length > maxExtLength &&
- maxExtLength > 0
- ) {
- nameParts[nameParts.length - 1] +=
- '.' +
- extension.substr(0, extension.length - maxExtLength);
- extension = extension.substr(-maxExtLength);
- }
- result.extension = maxExtLength ? extension : '';
- result.name = nameParts.join('.');
- return result;
- };
- /**
- * Parse file name and extension.
- * @param opts {Object} - middleware options.
- * @param fileName {String} - Uploaded file name.
- * @returns {String}
- */
- const parseFileName = (opts, fileName) => {
- if (!opts.safeFileNames) {
- return fileName;
- }
- // Set regular expression for the file name.
- let safeNameRegex = typeof opts.safeFileNames === 'object' && opts.safeFileNames instanceof RegExp
- ? opts.safeFileNames
- : SAFE_FILE_NAME_REGEX;
- // Parse file name extension.
- let {name, extension} = parseFileNameExtension(opts.preserveExtension, fileName);
- if (extension.length) extension = '.' + extension.replace(safeNameRegex, '');
- return name.replace(safeNameRegex, '').concat(extension);
- };
- module.exports = {
- debugLog,
- isFunc,
- errorFunc,
- promiseCallback,
- buildOptions,
- buildFields,
- checkAndMakeDir,
- deleteFile, // For testing purpose.
- copyFile, // For testing purpose.
- moveFile,
- saveBufferToFile,
- parseFileName,
- getTempFilename
- };
|