Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
226 lines
7.8 KiB
Plaintext
226 lines
7.8 KiB
Plaintext
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(path.resolve(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 (ignore) {
|
|
const matcher = /(%[a-f\d]{2})/gi;
|
|
return fileName.split(matcher).map((str)=>{
|
|
try {
|
|
return decodeURIComponent(str);
|
|
} catch (ignore) {
|
|
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 |