UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

228 lines (227 loc) • 8 kB
// @ts-strict-ignore import fs from 'fs'; import path from 'path'; import { Readable } from 'stream'; // Parameters for safe file name parsing. const SAFE_FILE_NAME_REGEX = /[^\w-]/g; const MAX_EXTENSION_LENGTH = 3; // Parameters to generate unique temporary file names: const TEMP_COUNTER_MAX = 65536; const TEMP_PREFIX = 'tmp'; let tempCounter = 0; /** * Logs message to console if options.debug option set to true. */ export const debugLog = (options, msg)=>{ const opts = options || {}; if (!opts.debug) { return false; } console.log(`Next-file-upload: ${msg}`) // eslint-disable-line ; return true; }; /** * Generates unique temporary file name. e.g. tmp-5000-156788789789. */ export const getTempFilename = (prefix = TEMP_PREFIX)=>{ tempCounter = tempCounter >= TEMP_COUNTER_MAX ? 1 : tempCounter + 1; return `${prefix}-${tempCounter}-${Date.now()}`; }; export const isFunc = (value)=>{ return typeof value === 'function'; }; const errorFunc = (resolve, reject)=>isFunc(reject) ? reject : resolve; export const promiseCallback = (resolve, reject)=>{ let hasFired = false; return (err)=>{ if (hasFired) { return; } hasFired = true; return err ? errorFunc(resolve, reject)(err) : resolve(); }; }; // The default prototypes for both objects and arrays. // Used by isSafeFromPollution const OBJECT_PROTOTYPE_KEYS = Object.getOwnPropertyNames(Object.prototype); const ARRAY_PROTOTYPE_KEYS = Object.getOwnPropertyNames(Array.prototype); export const isSafeFromPollution = (base, key)=>{ // We perform an instanceof check instead of Array.isArray as the former is more // permissive for cases in which the object as an Array prototype but was not constructed // via an Array constructor or literal. const TOUCHES_ARRAY_PROTOTYPE = base instanceof Array && ARRAY_PROTOTYPE_KEYS.includes(key); const TOUCHES_OBJECT_PROTOTYPE = OBJECT_PROTOTYPE_KEYS.includes(key); return !TOUCHES_ARRAY_PROTOTYPE && !TOUCHES_OBJECT_PROTOTYPE; }; export const buildFields = (instance, field, value)=>{ // Do nothing if value is not set. if (value === null || value === undefined) { return instance; } instance = instance || Object.create(null); if (!isSafeFromPollution(instance, field)) { return instance; } // Non-array fields if (!instance[field]) { instance[field] = value; return instance; } // Array fields if (instance[field] instanceof Array) { instance[field].push(value); } else { instance[field] = [ instance[field], value ]; } return instance; }; export const checkAndMakeDir = (fileUploadOptions, filePath)=>{ if (!fileUploadOptions.createParentPath) { return false; } // Check whether folder for the file exists. const parentPath = path.dirname(filePath); // Create folder if it doesn't exist. if (!fs.existsSync(parentPath)) { fs.mkdirSync(parentPath, { recursive: true }); } // Checks folder again and return a results. return fs.existsSync(parentPath); }; export const deleteFile = (filePath, callback)=>fs.unlink(filePath, callback); const copyFile = (src, dst, callback)=>{ // cbCalled flag and runCb helps to run cb only once. let cbCalled = false; const runCb = (err)=>{ if (cbCalled) { return; } cbCalled = true; callback(err); }; // Create read stream const readable = fs.createReadStream(src); readable.on('error', runCb); // Create write stream const writable = fs.createWriteStream(dst); writable.on('error', (err)=>{ readable.destroy(); runCb(err); }); writable.on('close', ()=>runCb()); // Copy file via piping streams. readable.pipe(writable); }; export const moveFile = (src, dst, callback)=>fs.rename(src, dst, (err)=>{ if (err) { // Try to copy file if rename didn't work. copyFile(src, dst, (cpErr)=>cpErr ? callback(cpErr) : deleteFile(src, callback)); return; } // File was renamed successfully: Add true to the callback to indicate that. callback(null, true); }); /** * Save buffer data to a file. * @param {Buffer} buffer - buffer to save to a file. * @param {string} filePath - path to a file. */ export const saveBufferToFile = (buffer, filePath, callback)=>{ if (!Buffer.isBuffer(buffer)) { return callback(new Error('buffer variable should be type of Buffer!')); } // Setup readable stream from buffer. let streamData = buffer; const readStream = new Readable(); readStream._read = ()=>{ readStream.push(streamData); streamData = null; }; // Setup file system writable stream. const fstream = fs.createWriteStream(filePath); // console.log("Calling saveBuffer"); fstream.on('error', (err)=>{ // console.log("err cb") callback(err); }); fstream.on('close', ()=>{ // console.log("close cb"); callback(); }); // Copy file via piping streams. readStream.pipe(fstream); }; /** * Decodes uriEncoded file names. * @param {Object} opts - middleware options. * @param fileName {String} - file name to decode. * @returns {String} */ const uriDecodeFileName = (opts, fileName)=>{ if (!opts || !opts.uriDecodeFileNames) { return fileName; } // Decode file name from URI with checking URI malformed errors. // See Issue https://github.com/richardgirges/express-fileupload/issues/342. try { return decodeURIComponent(fileName); } catch (err) { const matcher = /(%[a-f\d]{2})/gi; return fileName.split(matcher).map((str)=>{ try { return decodeURIComponent(str); } catch (err) { return ''; } }).join(''); } }; export const parseFileNameExtension = (preserveExtension, fileName)=>{ const defaultResult = { name: fileName, extension: '' }; if (!preserveExtension) { return defaultResult; } // Define maximum extension length const maxExtLength = typeof preserveExtension === 'boolean' ? MAX_EXTENSION_LENGTH : preserveExtension; const nameParts = fileName.split('.'); if (nameParts.length < 2) { return defaultResult; } let extension = nameParts.pop(); if (extension.length > maxExtLength && maxExtLength > 0) { nameParts[nameParts.length - 1] += '.' + extension.substr(0, extension.length - maxExtLength); extension = extension.substr(-maxExtLength); } return { name: nameParts.join('.'), extension: maxExtLength ? extension : '' }; }; export const parseFileName = (opts, fileName)=>{ // Check fileName argument if (!fileName || typeof fileName !== 'string') { return getTempFilename(); } // Cut off file name if it's length more then 255. let parsedName = fileName.length <= 255 ? fileName : fileName.substr(0, 255); // Decode file name if uriDecodeFileNames option set true. parsedName = uriDecodeFileName(opts, parsedName); // Stop parsing file name if safeFileNames options hasn't been set. if (!opts.safeFileNames) { return parsedName; } // Set regular expression for the file name. const nameRegex = typeof opts.safeFileNames === 'object' && opts.safeFileNames instanceof RegExp ? opts.safeFileNames : SAFE_FILE_NAME_REGEX; // Parse file name extension. const parsedFileName = parseFileNameExtension(opts.preserveExtension, parsedName); if (parsedFileName.extension.length) { parsedFileName.extension = '.' + parsedFileName.extension.replace(nameRegex, ''); } return parsedFileName.name.replace(nameRegex, '').concat(parsedFileName.extension); }; //# sourceMappingURL=utilities.js.map