payload
Version:
Node, React, Headless CMS and Application Framework built on Next.js
228 lines (227 loc) • 8 kB
JavaScript
// @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