diff --git a/frontend/public/index.html b/frontend/public/index.html index fc48d82..41caafb 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -18,6 +18,8 @@ + + *myFileName.ext*

**Example #2 (max extension length 2, extension shifted):**
app.use(fileUpload({ safeFileNames: true, preserveExtension: 2 }));
*myFileName.ext* --> *myFileNamee.xt* +abortOnLimit | | Returns a HTTP 413 when the file is bigger than the size limit if true. Otherwise, it will add a truncated = true to the resulting file structure. +responseOnLimit | | Response which will be send to client if file size limit exceeded when abortOnLimit set to true. +limitHandler | | User defined limit handler which will be invoked if the file is bigger than configured limits. +useTempFiles | | By default this module uploads files into RAM. Setting this option to True turns on using temporary files instead of utilising RAM. This avoids memory overflow issues when uploading large files or in case of uploading lots of files at same time. +tempFileDir | | Path to store temporary files.
Used along with the useTempFiles option. By default this module uses 'tmp' folder in the current working directory.
You can use trailing slash, but it is not necessary. +parseNested | | By default, req.body and req.files are flattened like this: {'name': 'John', 'hobbies[0]': 'Cinema', 'hobbies[1]': 'Bike'}

When this option is enabled they are parsed in order to be nested like this: {'name': 'John', 'hobbies': ['Cinema', 'Bike']} +debug | | Turn on/off upload process logging. Can be useful for troubleshooting. +uploadTimeout | | This defines how long to wait for data before aborting. Set to 0 if you want to turn off timeout checks. + +# Help Wanted +Looking for additional maintainers. Please contact `richardgirges [ at ] gmail.com` if you're interested. Pull Requests are welcomed! + +# Thanks & Credit +[Brian White](https://github.com/mscdex) for his stellar work on the [Busboy Package](https://github.com/mscdex/busboy) and the [connect-busboy Package](https://github.com/mscdex/connect-busboy) diff --git a/server/node_modules/express-fileupload/example/README.md b/server/node_modules/express-fileupload/example/README.md new file mode 100644 index 0000000..13d6f92 --- /dev/null +++ b/server/node_modules/express-fileupload/example/README.md @@ -0,0 +1,66 @@ +# express-fileupload Examples + +## Basic File Upload +**Your node.js code:** +```javascript +const express = require('express'); +const fileUpload = require('express-fileupload'); +const app = express(); + +// default options +app.use(fileUpload()); + +app.post('/upload', function(req, res) { + if (!req.files || Object.keys(req.files).length === 0) { + return res.status(400).send('No files were uploaded.'); + } + + // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file + let sampleFile = req.files.sampleFile; + + // Use the mv() method to place the file somewhere on your server + sampleFile.mv('/somewhere/on/your/server/filename.jpg', function(err) { + if (err) + return res.status(500).send(err); + + res.send('File uploaded!'); + }); +}); +``` + +**Your HTML file upload form:** +```html + + +
+ + +
+ + +``` + +## Multi-File Upload +express-fileupload supports multiple file uploads at the same time. + +Let's say you have three files in your form, each of the inputs with the name `my_profile_pic`, `my_pet`, and `my_cover_photo`: +```html + + + +``` + +These uploaded files would be accessible like so: +```javascript +app.post('/upload', function(req, res) { + // Uploaded files: + console.log(req.files.my_profile_pic.name); + console.log(req.files.my_pet.name); + console.log(req.files.my_cover_photo.name); +}); +``` + diff --git a/server/node_modules/express-fileupload/example/index.html b/server/node_modules/express-fileupload/example/index.html new file mode 100644 index 0000000..26630f6 --- /dev/null +++ b/server/node_modules/express-fileupload/example/index.html @@ -0,0 +1,12 @@ + + +
+ + +
+ + diff --git a/server/node_modules/express-fileupload/example/server.js b/server/node_modules/express-fileupload/example/server.js new file mode 100644 index 0000000..e8c736c --- /dev/null +++ b/server/node_modules/express-fileupload/example/server.js @@ -0,0 +1,41 @@ +const express = require('express'); +const fileUpload = require('../lib/index'); +const app = express(); + +const PORT = 8000; +app.use('/form', express.static(__dirname + '/index.html')); + +// default options +app.use(fileUpload()); + +app.get('/ping', function(req, res) { + res.send('pong'); +}); + +app.post('/upload', function(req, res) { + let sampleFile; + let uploadPath; + + if (!req.files || Object.keys(req.files).length === 0) { + res.status(400).send('No files were uploaded.'); + return; + } + + console.log('req.files >>>', req.files); // eslint-disable-line + + sampleFile = req.files.sampleFile; + + uploadPath = __dirname + '/uploads/' + sampleFile.name; + + sampleFile.mv(uploadPath, function(err) { + if (err) { + return res.status(500).send(err); + } + + res.send('File uploaded to ' + uploadPath); + }); +}); + +app.listen(PORT, function() { + console.log('Express server listening on port ', PORT); // eslint-disable-line +}); diff --git a/server/node_modules/express-fileupload/example/uploads/placeholder.txt b/server/node_modules/express-fileupload/example/uploads/placeholder.txt new file mode 100644 index 0000000..32172e2 --- /dev/null +++ b/server/node_modules/express-fileupload/example/uploads/placeholder.txt @@ -0,0 +1 @@ +files are placed here when uploaded using the upload.test.js express server \ No newline at end of file diff --git a/server/node_modules/express-fileupload/lib/fileFactory.js b/server/node_modules/express-fileupload/lib/fileFactory.js new file mode 100644 index 0000000..02b82df --- /dev/null +++ b/server/node_modules/express-fileupload/lib/fileFactory.js @@ -0,0 +1,65 @@ +'use strict'; + +const { + isFunc, + debugLog, + moveFile, + promiseCallback, + checkAndMakeDir, + saveBufferToFile +} = require('./utilities'); + +/** + * Returns Local function that moves the file to a different location on the filesystem + * which takes two function arguments to make it compatible w/ Promise or Callback APIs + * @param {String} filePath - destination file path. + * @param {Object} options - file factory options. + * @param {Object} fileUploadOptions - middleware options. + * @returns {Function} + */ +const moveFromTemp = (filePath, options, fileUploadOptions) => (resolve, reject) => { + debugLog(fileUploadOptions, `Moving temporary file ${options.tempFilePath} to ${filePath}`); + moveFile(options.tempFilePath, filePath, promiseCallback(resolve, reject)); +}; + +/** + * Returns Local function that moves the file from buffer to a different location on the filesystem + * which takes two function arguments to make it compatible w/ Promise or Callback APIs + * @param {String} filePath - destination file path. + * @param {Object} options - file factory options. + * @param {Object} fileUploadOptions - middleware options. + * @returns {Function} + */ +const moveFromBuffer = (filePath, options, fileUploadOptions) => (resolve, reject) => { + debugLog(fileUploadOptions, `Moving uploaded buffer to ${filePath}`); + saveBufferToFile(options.buffer, filePath, promiseCallback(resolve, reject)); +}; + +module.exports = (options, fileUploadOptions = {}) => { + // see: https://github.com/richardgirges/express-fileupload/issues/14 + // firefox uploads empty file in case of cache miss when f5ing page. + // resulting in unexpected behavior. if there is no file data, the file is invalid. + // if (!fileUploadOptions.useTempFiles && !options.buffer.length) return; + + // Create and return file object. + return { + name: options.name, + data: options.buffer, + size: options.size, + encoding: options.encoding, + tempFilePath: options.tempFilePath, + truncated: options.truncated, + mimetype: options.mimetype, + md5: options.hash, + mv: (filePath, callback) => { + // Define a propper move function. + const moveFunc = fileUploadOptions.useTempFiles + ? moveFromTemp(filePath, options, fileUploadOptions) + : moveFromBuffer(filePath, options, fileUploadOptions); + // Create a folder for a file. + checkAndMakeDir(fileUploadOptions, filePath); + // If callback is passed in, use the callback API, otherwise return a promise. + return isFunc(callback) ? moveFunc(callback) : new Promise(moveFunc); + } + }; +}; diff --git a/server/node_modules/express-fileupload/lib/index.js b/server/node_modules/express-fileupload/lib/index.js new file mode 100644 index 0000000..41437a6 --- /dev/null +++ b/server/node_modules/express-fileupload/lib/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const path = require('path'); +const processMultipart = require('./processMultipart'); +const isEligibleRequest = require('./isEligibleRequest'); +const { buildOptions, debugLog } = require('./utilities'); + +const DEFAULT_OPTIONS = { + debug: false, + uploadTimeout: 60000, + fileHandler: false, + uriDecodeFileNames: false, + safeFileNames: false, + preserveExtension: false, + abortOnLimit: false, + responseOnLimit: 'File size limit has been reached', + limitHandler: false, + createParentPath: false, + parseNested: false, + useTempFiles: false, + tempFileDir: path.join(process.cwd(), 'tmp') +}; + +/** + * Expose the file upload middleware + * @param {Object} options - Middleware options. + * @returns {Function} - express-fileupload middleware. + */ +module.exports = (options) => { + const uploadOptions = buildOptions(DEFAULT_OPTIONS, options); + return (req, res, next) => { + if (!isEligibleRequest(req)) { + debugLog(uploadOptions, 'Request is not eligible for file upload!'); + return next(); + } + processMultipart(uploadOptions, req, res, next); + }; +}; diff --git a/server/node_modules/express-fileupload/lib/isEligibleRequest.js b/server/node_modules/express-fileupload/lib/isEligibleRequest.js new file mode 100644 index 0000000..0c25283 --- /dev/null +++ b/server/node_modules/express-fileupload/lib/isEligibleRequest.js @@ -0,0 +1,34 @@ +const ACCEPTABLE_CONTENT_TYPE = /^(multipart\/.+);(.*)$/i; +const UNACCEPTABLE_METHODS = ['GET', 'HEAD']; + +/** + * Ensures the request contains a content body + * @param {Object} req Express req object + * @returns {Boolean} + */ +const hasBody = (req) => { + return ('transfer-encoding' in req.headers) || + ('content-length' in req.headers && req.headers['content-length'] !== '0'); +}; + +/** + * Ensures the request is not using a non-compliant multipart method + * such as GET or HEAD + * @param {Object} req Express req object + * @returns {Boolean} + */ +const hasAcceptableMethod = req => !UNACCEPTABLE_METHODS.includes(req.method); + +/** + * Ensures that only multipart requests are processed by express-fileupload + * @param {Object} req Express req object + * @returns {Boolean} + */ +const hasAcceptableContentType = req => ACCEPTABLE_CONTENT_TYPE.test(req.headers['content-type']); + +/** + * Ensures that the request in question is eligible for file uploads + * @param {Object} req Express req object + * @returns {Boolean} + */ +module.exports = req => hasBody(req) && hasAcceptableMethod(req) && hasAcceptableContentType(req); diff --git a/server/node_modules/express-fileupload/lib/memHandler.js b/server/node_modules/express-fileupload/lib/memHandler.js new file mode 100644 index 0000000..09accfe --- /dev/null +++ b/server/node_modules/express-fileupload/lib/memHandler.js @@ -0,0 +1,42 @@ +const crypto = require('crypto'); +const { debugLog } = require('./utilities'); + +/** + * memHandler - In memory upload handler + * @param {Object} options + * @param {String} fieldname + * @param {String} filename + * @returns {Object} + */ +module.exports = (options, fieldname, filename) => { + const buffers = []; + const hash = crypto.createHash('md5'); + let fileSize = 0; + let completed = false; + + const getBuffer = () => Buffer.concat(buffers, fileSize); + + return { + dataHandler: (data) => { + if (completed === true) { + debugLog(options, `Error: got ${fieldname}->${filename} data chunk for completed upload!`); + return; + } + buffers.push(data); + hash.update(data); + fileSize += data.length; + debugLog(options, `Uploading ${fieldname}->${filename}, bytes:${fileSize}...`); + }, + getBuffer: getBuffer, + getFilePath: () => '', + getFileSize: () => fileSize, + getHash: () => hash.digest('hex'), + complete: () => { + debugLog(options, `Upload ${fieldname}->${filename} completed, bytes:${fileSize}.`); + completed = true; + return getBuffer(); + }, + cleanup: () => { completed = true; }, + getWritePromise: () => Promise.resolve() + }; +}; diff --git a/server/node_modules/express-fileupload/lib/processMultipart.js b/server/node_modules/express-fileupload/lib/processMultipart.js new file mode 100644 index 0000000..1561c0b --- /dev/null +++ b/server/node_modules/express-fileupload/lib/processMultipart.js @@ -0,0 +1,154 @@ +const Busboy = require('busboy'); +const UploadTimer = require('./uploadtimer'); +const fileFactory = require('./fileFactory'); +const memHandler = require('./memHandler'); +const tempFileHandler = require('./tempFileHandler'); +const processNested = require('./processNested'); +const { + isFunc, + debugLog, + buildFields, + buildOptions, + parseFileName +} = require('./utilities'); + +const waitFlushProperty = Symbol('wait flush property symbol'); + +/** + * Processes multipart request + * Builds a req.body object for fields + * Builds a req.files object for files + * @param {Object} options expressFileupload and Busboy options + * @param {Object} req Express request object + * @param {Object} res Express response object + * @param {Function} next Express next method + * @return {void} + */ +module.exports = (options, req, res, next) => { + req.files = null; + + // Build busboy options and init busboy instance. + const busboyOptions = buildOptions(options, { headers: req.headers }); + const busboy = new Busboy(busboyOptions); + + // Close connection with specified reason and http code, default: 400 Bad Request. + const closeConnection = (code, reason) => { + req.unpipe(busboy); + res.writeHead(code || 400, { Connection: 'close' }); + res.end(reason || 'Bad Request'); + }; + + // Build multipart req.body fields + busboy.on('field', (field, val) => req.body = buildFields(req.body, field, val)); + + // Build req.files fields + busboy.on('file', (field, file, name, encoding, mime) => { + // Parse file name(cutting huge names, decoding, etc..). + const filename = parseFileName(options, name); + // Define methods and handlers for upload process. + const { + dataHandler, + getFilePath, + getFileSize, + getHash, + complete, + cleanup, + getWritePromise + } = options.useTempFiles + ? tempFileHandler(options, field, filename) // Upload into temporary file. + : memHandler(options, field, filename); // Upload into RAM. + // Define upload timer. + const uploadTimer = new UploadTimer(options.uploadTimeout, () => { + file.removeAllListeners('data'); + file.resume(); + // After destroy an error event will be emitted and file clean up will be done. + file.destroy(new Error(`Upload timeout ${field}->${filename}, bytes:${getFileSize()}`)); + }); + + file.on('limit', () => { + debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`); + // Reset upload timer in case of file limit reached. + uploadTimer.clear(); + // Run a user defined limit handler if it has been set. + if (isFunc(options.limitHandler)) return options.limitHandler(req, res, next); + // Close connection with 413 code and do cleanup if abortOnLimit set(default: false). + if (options.abortOnLimit) { + debugLog(options, `Aborting upload because of size limit ${field}->${filename}.`); + closeConnection(413, options.responseOnLimit); + cleanup(); + } + }); + + file.on('data', (data) => { + uploadTimer.set(); // Refresh upload timer each time new data chunk came. + dataHandler(data); // Handle new piece of data. + }); + + file.on('end', () => { + const size = getFileSize(); + // Debug logging for file upload ending. + debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`); + // Reset upload timer in case of end event. + uploadTimer.clear(); + // See https://github.com/richardgirges/express-fileupload/issues/191 + // Do not add file instance to the req.files if original name and size are empty. + // Empty name and zero size indicates empty file field in the posted form. + if (!name && size === 0) { + return debugLog(options, `Don't add file instance if original name and size are empty`); + } + req.files = buildFields(req.files, field, fileFactory({ + buffer: complete(), + name: filename, + tempFilePath: getFilePath(), + hash: getHash(), + size, + encoding, + truncated: file.truncated, + mimetype: mime + }, options)); + + if (!req[waitFlushProperty]) { + req[waitFlushProperty] = []; + } + req[waitFlushProperty].push(getWritePromise()); + }); + + file.on('error', (err) => { + uploadTimer.clear(); // Reset upload timer in case of errors. + debugLog(options, err); + cleanup(); + next(); + }); + + // Debug logging for a new file upload. + debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`); + // Set new upload timeout for a new file. + uploadTimer.set(); + }); + + busboy.on('finish', () => { + debugLog(options, `Busboy finished parsing request.`); + if (options.parseNested) { + req.body = processNested(req.body); + req.files = processNested(req.files); + } + + if (!req[waitFlushProperty]) return next(); + Promise.all(req[waitFlushProperty]) + .then(() => { + delete req[waitFlushProperty]; + next(); + }).catch(err => { + delete req[waitFlushProperty]; + debugLog(options, `Error while waiting files flush: ${err}`); + next(err); + }); + }); + + busboy.on('error', (err) => { + debugLog(options, `Busboy error`); + next(err); + }); + + req.pipe(busboy); +}; diff --git a/server/node_modules/express-fileupload/lib/processNested.js b/server/node_modules/express-fileupload/lib/processNested.js new file mode 100644 index 0000000..83ef371 --- /dev/null +++ b/server/node_modules/express-fileupload/lib/processNested.js @@ -0,0 +1,28 @@ +module.exports = function(data){ + if (!data || data.length < 1) return {}; + + let d = {}, + keys = Object.keys(data); + + for (let i = 0; i < keys.length; i++) { + let key = keys[i], + value = data[key], + current = d, + keyParts = key + .replace(new RegExp(/\[/g), '.') + .replace(new RegExp(/\]/g), '') + .split('.'); + + for (let index = 0; index < keyParts.length; index++){ + let k = keyParts[index]; + if (index >= keyParts.length - 1){ + current[k] = value; + } else { + if (!current[k]) current[k] = !isNaN(keyParts[index + 1]) ? [] : {}; + current = current[k]; + } + } + } + + return d; +}; \ No newline at end of file diff --git a/server/node_modules/express-fileupload/lib/tempFileHandler.js b/server/node_modules/express-fileupload/lib/tempFileHandler.js new file mode 100644 index 0000000..bfaca8b --- /dev/null +++ b/server/node_modules/express-fileupload/lib/tempFileHandler.js @@ -0,0 +1,64 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const { + debugLog, + checkAndMakeDir, + getTempFilename, + deleteFile +} = require('./utilities'); + +module.exports = (options, fieldname, filename) => { + const dir = path.normalize(options.tempFileDir); + const tempFilePath = path.join(dir, getTempFilename()); + checkAndMakeDir({ createParentPath: true }, tempFilePath); + + debugLog(options, `Temporary file path is ${tempFilePath}`); + + const hash = crypto.createHash('md5'); + let fileSize = 0; + let completed = false; + + debugLog(options, `Opening write stream for ${fieldname}->${filename}...`); + const writeStream = fs.createWriteStream(tempFilePath); + const writePromise = new Promise((resolve, reject) => { + writeStream.on('finish', () => resolve()); + writeStream.on('error', (err) => { + debugLog(options, `Error write temp file: ${err}`); + reject(err); + }); + }); + + return { + dataHandler: (data) => { + if (completed === true) { + debugLog(options, `Error: got ${fieldname}->${filename} data chunk for completed upload!`); + return; + } + writeStream.write(data); + hash.update(data); + fileSize += data.length; + debugLog(options, `Uploading ${fieldname}->${filename}, bytes:${fileSize}...`); + }, + getFilePath: () => tempFilePath, + getFileSize: () => fileSize, + getHash: () => hash.digest('hex'), + complete: () => { + completed = true; + debugLog(options, `Upload ${fieldname}->${filename} completed, bytes:${fileSize}.`); + if (writeStream !== false) writeStream.end(); + // Return empty buff since data was uploaded into a temp file. + return Buffer.concat([]); + }, + cleanup: () => { + completed = true; + debugLog(options, `Cleaning up temporary file ${tempFilePath}...`); + writeStream.end(); + deleteFile(tempFilePath, err => (err + ? debugLog(options, `Cleaning up temporary file ${tempFilePath} failed: ${err}`) + : debugLog(options, `Cleaning up temporary file ${tempFilePath} done.`) + )); + }, + getWritePromise: () => writePromise + }; +}; diff --git a/server/node_modules/express-fileupload/lib/uploadtimer.js b/server/node_modules/express-fileupload/lib/uploadtimer.js new file mode 100644 index 0000000..d29ab46 --- /dev/null +++ b/server/node_modules/express-fileupload/lib/uploadtimer.js @@ -0,0 +1,26 @@ +class UploadTimer { + /** + * @constructor + * @param {number} timeout - timer timeout in msecs. + * @param {Function} callback - callback to run when timeout reached. + */ + constructor(timeout = 0, callback = () => {}) { + this.timeout = timeout; + this.callback = callback; + this.timer = null; + } + + clear() { + clearTimeout(this.timer); + } + + set() { + // Do not start a timer if zero timeout or it hasn't been set. + if (!this.timeout) return false; + this.clear(); + this.timer = setTimeout(this.callback, this.timeout); + return true; + } +} + +module.exports = UploadTimer; diff --git a/server/node_modules/express-fileupload/lib/utilities.js b/server/node_modules/express-fileupload/lib/utilities.js new file mode 100644 index 0000000..9f12820 --- /dev/null +++ b/server/node_modules/express-fileupload/lib/utilities.js @@ -0,0 +1,270 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { Readable } = require('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 debug option set to true. + * @param {Object} options - options object. + * @param {string} msg - message to log. + * @returns {boolean} - false if debug is off. + */ +const debugLog = (options, msg) => { + const opts = options || {}; + if (!opts.debug) return false; + console.log(`Express-file-upload: ${msg}`); // eslint-disable-line + return true; +}; + +/** + * Generates unique temporary file name. e.g. tmp-5000-156788789789. + * @param {string} prefix - a prefix for generated unique file name. + * @returns {string} + */ +const getTempFilename = (prefix = TEMP_PREFIX) => { + tempCounter = tempCounter >= TEMP_COUNTER_MAX ? 1 : tempCounter + 1; + return `${prefix}-${tempCounter}-${Date.now()}`; +}; + +/** + * isFunc: Checks if argument is a function. + * @returns {boolean} - Returns true if argument is a function. + */ +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 => err ? errorFunc(resolve, reject)(err) : resolve(); +}; + +/** + * Builds instance options from arguments objects(can't be arrow function). + * @returns {Object} - result options. + */ +const buildOptions = function() { + const result = {}; + [...arguments].forEach(options => { + if (!options || typeof options !== 'object') return; + Object.keys(options).forEach(i => result[i] = options[i]); + }); + return result; +}; + +/** + * Builds request fields (using to build req.body and req.files) + * @param {Object} instance - request object. + * @param {string} field - field name. + * @param {any} 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; + return instance; + } + // 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 + * @returns {boolean} + */ +const checkAndMakeDir = (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 doesn't exist. + if (!fs.existsSync(parentPath)) fs.mkdirSync(parentPath, { recursive: true }); + // Checks folder again and return a results. + return fs.existsSync(parentPath); +}; + +/** + * Deletes a file. + * @param {string} file - Path to the file to delete. + * @param {Function} callback + */ +const deleteFile = (file, callback) => fs.unlink(file, callback); + +/** + * Copy file via streams + * @param {string} src - Path to the source file + * @param {string} dst - Path to the destination file. + */ +const copyFile = (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); +}; + +/** + * moveFile: moves the file from src to dst. + * Firstly trying to rename the file if no luck copying it to dst and then 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) => fs.rename(src, dst, err => (err + ? copyFile(src, dst, err => err ? callback(err) : deleteFile(src, callback)) + : 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 = (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; + let readStream = Readable(); + readStream._read = () => { + readStream.push(streamData); + streamData = null; + }; + // Setup file system writable stream. + let fstream = fs.createWriteStream(filePath); + fstream.on('error', err => callback(err)); + fstream.on('close', () => callback()); + // Copy file via piping streams. + readStream.pipe(fstream); +}; + +/** + * Decodes uriEncoded file names. + * @param fileName {String} - file name to decode. + * @returns {String} + */ +const uriDecodeFileName = (opts, fileName) => { + return opts.uriDecodeFileNames ? decodeURIComponent(fileName) : fileName; +}; + +/** + * Parses filename and extension and returns object {name, extension}. + * @param {boolean|integer} preserveExtension - true/false or number of characters for extension. + * @param {string} fileName - file name to parse. + * @returns {Object} - { name, extension }. + */ +const parseFileNameExtension = (preserveExtension, fileName) => { + const preserveExtensionLengh = parseInt(preserveExtension); + const result = {name: fileName, extension: ''}; + if (!preserveExtension && preserveExtensionLengh !== 0) return result; + // Define maximum extension length + const maxExtLength = isNaN(preserveExtensionLengh) + ? MAX_EXTENSION_LENGTH + : Math.abs(preserveExtensionLengh); + + const 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 {Object} opts - middleware options. + * @param {string} fileName - Uploaded file name. + * @returns {string} + */ +const parseFileName = (opts, fileName) => { + // Check fileName argument + if (!fileName || typeof fileName !== 'string') return getTempFilename(); + // Cut off file name if it's lenght 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. + let {name, extension} = parseFileNameExtension(opts.preserveExtension, parsedName); + if (extension.length) extension = '.' + extension.replace(nameRegex, ''); + + return name.replace(nameRegex, '').concat(extension); +}; + +module.exports = { + isFunc, + debugLog, + copyFile, // For testing purpose. + moveFile, + errorFunc, + deleteFile, // For testing purpose. + buildFields, + buildOptions, + parseFileName, + getTempFilename, + promiseCallback, + checkAndMakeDir, + saveBufferToFile, + uriDecodeFileName +}; diff --git a/server/node_modules/express-fileupload/package.json b/server/node_modules/express-fileupload/package.json new file mode 100644 index 0000000..ba00c22 --- /dev/null +++ b/server/node_modules/express-fileupload/package.json @@ -0,0 +1,77 @@ +{ + "_from": "express-fileupload", + "_id": "express-fileupload@1.1.7-alpha.4", + "_inBundle": false, + "_integrity": "sha512-uNl/TB3adUH25cDRp1gDoXQ38SdIZXOAVzC54G/xnOAa4M3maBWiZTVz39cnoQ7TXhmYXYpnOfMDMbqSelXFmQ==", + "_location": "/express-fileupload", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "express-fileupload", + "name": "express-fileupload", + "escapedName": "express-fileupload", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.7-alpha.4.tgz", + "_shasum": "c87dcb7880fe2e5a1d15ea97a225719c5ac0a30d", + "_spec": "express-fileupload", + "_where": "/home/sigonasr2/divar/server", + "author": { + "name": "Richard Girges", + "email": "richardgirges@gmail.com" + }, + "bugs": { + "url": "https://github.com/richardgirges/express-fileupload/issues" + }, + "bundleDependencies": false, + "dependencies": { + "busboy": "^0.3.1" + }, + "deprecated": false, + "description": "Simple express file upload middleware that wraps around Busboy", + "devDependencies": { + "body-parser": "^1.19.0", + "coveralls": "^3.0.14", + "eslint": "^6.8.0", + "express": "^4.17.1", + "istanbul": "^0.4.5", + "md5": "^2.2.1", + "mocha": "^7.2.0", + "rimraf": "^3.0.2", + "supertest": "^4.0.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "homepage": "https://github.com/richardgirges/express-fileupload#readme", + "keywords": [ + "express", + "file-upload", + "upload", + "forms", + "multipart", + "files", + "busboy", + "middleware" + ], + "license": "MIT", + "main": "./lib/index", + "name": "express-fileupload", + "repository": { + "type": "git", + "url": "git+https://github.com/richardgirges/express-fileupload.git" + }, + "scripts": { + "coveralls": "cat ./coverage/lcov.info | coveralls", + "lint": "eslint ./", + "test": "istanbul cover node_modules/mocha/bin/_mocha -- -R spec" + }, + "version": "1.1.7-alpha.4" +} diff --git a/server/node_modules/express-fileupload/test/fileFactory.spec.js b/server/node_modules/express-fileupload/test/fileFactory.spec.js new file mode 100644 index 0000000..350158e --- /dev/null +++ b/server/node_modules/express-fileupload/test/fileFactory.spec.js @@ -0,0 +1,78 @@ +'use strict'; + +const fs = require('fs'); +const md5 = require('md5'); +const path = require('path'); +const assert = require('assert'); +const server = require('./server'); +const {isFunc} = require('../lib/utilities'); +const fileFactory = require('../lib/fileFactory'); + +const mockFileName = 'basketball.png'; +const mockFile = path.join(server.fileDir, mockFileName); +const mockBuffer = fs.readFileSync(mockFile); +const mockMd5 = md5(mockBuffer); + +const mockFileOpts = { + name: mockFileName, + buffer: mockBuffer, + encoding: 'utf-8', + mimetype: 'image/png', + hash: mockMd5, + tempFilePath: mockFile +}; + +describe('Test of the fileFactory factory', function() { + beforeEach(() => server.clearUploadsDir()); + + it('return a file object', () => assert.ok(fileFactory(mockFileOpts))); + + describe('Properties', function() { + it('contains the name property', () => { + assert.equal(fileFactory(mockFileOpts).name, mockFileName); + }); + it('contains the data property', () => assert.ok(fileFactory(mockFileOpts).data)); + it('contains the encoding property', () => { + assert.equal(fileFactory(mockFileOpts).encoding, 'utf-8'); + }); + it('contains the mimetype property', () => { + assert.equal(fileFactory(mockFileOpts).mimetype, 'image/png'); + }); + it('contains the md5 property', () => assert.equal(fileFactory(mockFileOpts).md5, mockMd5)); + it('contains the mv method', () => assert.equal(isFunc(fileFactory(mockFileOpts).mv), true)); + }); + + describe('File object behavior for in memory upload', function() { + const file = fileFactory(mockFileOpts); + it('move the file to the specified folder', (done) => { + file.mv(path.join(server.uploadDir, mockFileName), (err) => { + assert.ifError(err); + done(); + }); + }); + it('reject the mv if the destination does not exists', (done) => { + file.mv(path.join(server.uploadDir, 'unknown', mockFileName), (err) => { + assert.ok(err); + done(); + }); + }); + }); + + describe('File object behavior for upload into temporary file', function() { + const file = fileFactory(mockFileOpts, { useTempFiles: true }); + it('move the file to the specified folder', (done) => { + file.mv(path.join(server.uploadDir, mockFileName), (err) => { + assert.ifError(err); + // Place back moved file. + fs.renameSync(path.join(server.uploadDir, mockFileName), mockFile); + done(); + }); + }); + it('reject the mv if the destination does not exists', (done) => { + file.mv(path.join(server.uploadDir, 'unknown', mockFileName), (err) => { + assert.ok(err); + done(); + }); + }); + }); +}); diff --git a/server/node_modules/express-fileupload/test/fileLimitUploads.spec.js b/server/node_modules/express-fileupload/test/fileLimitUploads.spec.js new file mode 100644 index 0000000..5c799ea --- /dev/null +++ b/server/node_modules/express-fileupload/test/fileLimitUploads.spec.js @@ -0,0 +1,95 @@ +'use strict'; + +const path = require('path'); +const request = require('supertest'); +const assert = require('assert'); +const server = require('./server'); +const clearUploadsDir = server.clearUploadsDir; +const fileDir = server.fileDir; + +describe('Test Single File Upload With File Size Limit', function() { + let app, limitHandlerRun; + + beforeEach(function() { + clearUploadsDir(); + }); + + describe('abort connection on limit reached', function() { + before(function() { + app = server.setup({ + limits: {fileSize: 200 * 1024}, // set 200kb upload limit + abortOnLimit: true + }); + }); + + it(`upload 'basketball.png' (~154kb) with 200kb size limit`, function(done) { + let filePath = path.join(fileDir, 'basketball.png'); + + request(app) + .post('/upload/single/truncated') + .attach('testFile', filePath) + .expect(200) + .end(done); + }); + + it(`fail when uploading 'car.png' (~269kb) with 200kb size limit`, function(done) { + let filePath = path.join(fileDir, 'car.png'); + + request(app) + .post('/upload/single/truncated') + .attach('testFile', filePath) + .expect(413) + .end(done); + }); + }); + + describe('Run limitHandler on limit reached.', function(){ + before(function() { + app = server.setup({ + limits: {fileSize: 200 * 1024}, // set 200kb upload limit + limitHandler: (req, res) => { // set limit handler + res.writeHead(500, { Connection: 'close', 'Content-Type': 'application/json'}); + res.end(JSON.stringify({response: 'Limit reached!'})); + limitHandlerRun = true; + } + }); + }); + + it(`Run limit handler when uploading 'car.png' (~269kb) with 200kb size limit`, function(done) { + let filePath = path.join(fileDir, 'car.png'); + limitHandlerRun = false; + + request(app) + .post('/upload/single/truncated') + .attach('testFile', filePath) + .expect(500, {response: 'Limit reached!'}) + .end(function(err){ + if (err) return done(err); + if (!limitHandlerRun) return done('handler did not run'); + done(); + }); + }); + + }); + + describe('pass truncated file to the next handler', function() { + before(function() { + app = server.setup({ + limits: {fileSize: 200 * 1024} // set 200kb upload limit + }); + }); + + it(`fail when uploading 'car.png' (~269kb) with 200kb size limit`, function(done) { + let filePath = path.join(fileDir, 'car.png'); + + request(app) + .post('/upload/single/truncated') + .attach('testFile', filePath) + .expect(400) + .end(function(err, res) { + assert.ok(res.error.text === 'File too big'); + done(); + }); + }); + }); +}); diff --git a/server/node_modules/express-fileupload/test/files/basket.ball.bp b/server/node_modules/express-fileupload/test/files/basket.ball.bp new file mode 100644 index 0000000..a2a1571 Binary files /dev/null and b/server/node_modules/express-fileupload/test/files/basket.ball.bp differ diff --git a/server/node_modules/express-fileupload/test/files/basketball.png b/server/node_modules/express-fileupload/test/files/basketball.png new file mode 100644 index 0000000..a2a1571 Binary files /dev/null and b/server/node_modules/express-fileupload/test/files/basketball.png differ diff --git a/server/node_modules/express-fileupload/test/files/car.png b/server/node_modules/express-fileupload/test/files/car.png new file mode 100644 index 0000000..510b859 Binary files /dev/null and b/server/node_modules/express-fileupload/test/files/car.png differ diff --git a/server/node_modules/express-fileupload/test/files/emptyfile.txt b/server/node_modules/express-fileupload/test/files/emptyfile.txt new file mode 100644 index 0000000..e69de29 diff --git a/server/node_modules/express-fileupload/test/files/my$Invalid#fileName.png123 b/server/node_modules/express-fileupload/test/files/my$Invalid#fileName.png123 new file mode 100644 index 0000000..510b859 Binary files /dev/null and b/server/node_modules/express-fileupload/test/files/my$Invalid#fileName.png123 differ diff --git a/server/node_modules/express-fileupload/test/files/tree.png b/server/node_modules/express-fileupload/test/files/tree.png new file mode 100644 index 0000000..e6ecd17 Binary files /dev/null and b/server/node_modules/express-fileupload/test/files/tree.png differ diff --git a/server/node_modules/express-fileupload/test/multipartFields.spec.js b/server/node_modules/express-fileupload/test/multipartFields.spec.js new file mode 100644 index 0000000..18bdae4 --- /dev/null +++ b/server/node_modules/express-fileupload/test/multipartFields.spec.js @@ -0,0 +1,85 @@ +'use strict'; + +const request = require('supertest'); +const server = require('./server'); +const app = server.setup(); + +let mockUser = { + firstName: 'Joe', + lastName: 'Schmo', + email: 'joe@mailinator.com' +}; + +let mockCars = [ + 'rsx', + 'tsx', + 'civic', + 'integra' +]; + +describe('Test Multipart Form Single Field Submissions', function() { + it('submit multipart user data with POST', function(done) { + request(app) + .post('/fields/user') + .field('firstName', mockUser.firstName) + .field('lastName', mockUser.lastName) + .field('email', mockUser.email) + .expect('Content-Type', /json/) + .expect(200, { + firstName: mockUser.firstName, + lastName: mockUser.lastName, + email: mockUser.email + }, done); + }); + + it('submit multipart user data with PUT', function(done) { + request(app) + .post('/fields/user') + .field('firstName', mockUser.firstName) + .field('lastName', mockUser.lastName) + .field('email', mockUser.email) + .expect('Content-Type', /json/) + .expect(200, { + firstName: mockUser.firstName, + lastName: mockUser.lastName, + email: mockUser.email + }, done); + }); + + it('fail when user data submitted without multipart', function(done) { + request(app) + .post('/fields/user') + .send(mockUser) + .expect(400) + .end(done); + }); + + it('fail when user data not submitted', function(done) { + request(app) + .post('/fields/user') + .expect(400) + .end(done); + }); +}); + +describe('Test Multipart Form Array Field Submissions', function() { + it('submit array of data with POST', function(done) { + let req = request(app).post('/fields/array'); + + for (let i = 0; i < mockCars.length; i++) { + req.field('testField', mockCars[i]); + } + + req + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + + let responseMatchesRequest = res.body.join(',') === mockCars.join(','); + + done(responseMatchesRequest ? null : 'Data was returned as expected.'); + }); + }); +}); diff --git a/server/node_modules/express-fileupload/test/multipartUploads.spec.js b/server/node_modules/express-fileupload/test/multipartUploads.spec.js new file mode 100644 index 0000000..4cefd60 --- /dev/null +++ b/server/node_modules/express-fileupload/test/multipartUploads.spec.js @@ -0,0 +1,451 @@ +'use strict'; + +const fs = require('fs'); +const md5 = require('md5'); +const path = require('path'); +const request = require('supertest'); +const server = require('./server'); + +const fileDir = server.fileDir; +const tempDir = server.tempDir; +const uploadDir = server.uploadDir; +const clearTempDir = server.clearTempDir; +const clearUploadsDir = server.clearUploadsDir; + +const mockFiles = ['car.png', 'tree.png', 'basketball.png', 'emptyfile.txt']; + +const mockUser = { + firstName: 'Joe', + lastName: 'Schmo', + email: 'joe@mailinator.com' +}; + +// Reset response body.uploadDir/uploadPath for testing. +const resetBodyUploadData = (res) => { + res.body.uploadDir = ''; + res.body.uploadPath = ''; +}; + +const genUploadResult = (fileName, filePath) => { + const fileStat = fs.statSync(filePath); + const fileBuffer = fs.readFileSync(filePath); + return { + name: fileName, + md5: md5(fileBuffer), + size: fileStat.size, + uploadDir: '', + uploadPath: '' + }; +}; + +describe('Test Directory Cleaning Method', function() { + it('emptied "uploads" directory', function(done) { + clearUploadsDir(); + const filesFound = fs.readdirSync(uploadDir).length; + done(filesFound ? `Directory not empty. Found ${filesFound} files.` : null); + }); +}); + +describe('Test Single File Upload', function() { + const app = server.setup(); + + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + const result = genUploadResult(fileName, filePath); + + it(`upload ${fileName} with POST`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + + it(`upload ${fileName} with PUT`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); + + it('fail when no files were attached', function(done) { + request(app) + .post('/upload/single') + .expect(400) + .end(done); + }); + + it('fail when using GET', function(done) { + request(app) + .get('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); + + it('fail when using HEAD', function(done) { + request(app) + .head('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); +}); + +describe('Test Single File Upload w/ .mv()', function() { + const app = server.setup(); + + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + const result = genUploadResult(fileName, filePath); + + it(`upload ${fileName} with POST w/ .mv()`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + + it(`upload ${fileName} with PUT w/ .mv()`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); +}); + +describe('Test Single File Upload with useTempFiles option.', function() { + const app = server.setup({ useTempFiles: true, tempFileDir: tempDir }); + + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + const result = genUploadResult(fileName, filePath); + + it(`upload ${fileName} with POST`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + + it(`upload ${fileName} with PUT`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); + + it('fail when no files were attached', function(done) { + request(app) + .post('/upload/single') + .expect(400) + .end(done); + }); + + it('fail when using GET', function(done) { + request(app) + .get('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); + + it('fail when using HEAD', function(done) { + request(app) + .head('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); +}); + +describe('Test Single File Upload with useTempFiles option and empty tempFileDir.', function() { + const app = server.setup({ useTempFiles: true, tempFileDir: '' }); + + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + const result = genUploadResult(fileName, filePath); + + it(`upload ${fileName} with POST`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); +}); + +describe('Test Single File Upload w/ .mv() Promise', function() { + const app = server.setup(); + + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + const result = genUploadResult(fileName, filePath); + + it(`upload ${fileName} with POST w/ .mv() Promise`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single/promise') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + + it(`upload ${fileName} with PUT w/ .mv() Promise`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single/promise') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); + + it('fail when no files were attached', function(done) { + request(app) + .post('/upload/single') + .expect(400) + .end(done); + }); + + it('fail when using GET', function(done) { + request(app) + .get('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); + + it('fail when using HEAD', function(done) { + request(app) + .head('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); +}); + +describe('Test Single File Upload w/ .mv() Promise and useTempFiles set to true', function() { + const app = server.setup({ useTempFiles: true, tempFileDir: tempDir }); + + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + const result = genUploadResult(fileName, filePath); + + it(`upload ${fileName} with POST w/ .mv() Promise`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single/promise') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + + it(`upload ${fileName} with PUT w/ .mv() Promise`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single/promise') + .attach('testFile', filePath) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); + + it('fail when no files were attached', (done) => { + request(app) + .post('/upload/single') + .expect(400) + .end(done); + }); + + it('fail when using GET', (done) => { + request(app) + .get('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); + + it('fail when using HEAD', (done) => { + request(app) + .head('/upload/single') + .attach('testFile', path.join(fileDir, mockFiles[0])) + .expect(400) + .end(done); + }); +}); + +describe('Test Multi-File Upload', function() { + const app = server.setup(); + + it('upload multiple files with POST', (done) => { + clearUploadsDir(); + const req = request(app).post('/upload/multiple'); + const expectedResult = []; + const expectedResultSorted = []; + const uploadedFilesPath = []; + mockFiles.forEach((fileName, index) => { + const filePath = path.join(fileDir, fileName); + req.attach(`testFile${index + 1}`, filePath); + uploadedFilesPath.push(path.join(uploadDir, fileName)); + expectedResult.push(genUploadResult(fileName, filePath)); + }); + + req + .expect((res) => { + res.body.forEach((fileInfo) => { + fileInfo.uploadDir = ''; + fileInfo.uploadPath = ''; + const index = mockFiles.indexOf(fileInfo.name); + expectedResultSorted.push(expectedResult[index]); + }); + }) + .expect(200, expectedResultSorted) + .end((err) => { + if (err) return done(err); + fs.stat(uploadedFilesPath[0], (err) => { + if (err) return done(err); + fs.stat(uploadedFilesPath[1], (err) => { + if (err) return done(err); + fs.stat(uploadedFilesPath[2], done); + }); + }); + }); + }); +}); + +describe('Test File Array Upload', function() { + const app = server.setup(); + + it('upload array of files with POST', (done) => { + clearUploadsDir(); + const req = request(app).post('/upload/array'); + const expectedResult = []; + const expectedResultSorted = []; + const uploadedFilesPath = []; + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + uploadedFilesPath.push(path.join(uploadDir, fileName)); + expectedResult.push(genUploadResult(fileName, filePath)); + req.attach('testFiles', filePath); + }); + + req + .expect((res)=>{ + res.body.forEach((fileInfo) => { + fileInfo.uploadDir = ''; + fileInfo.uploadPath = ''; + const index = mockFiles.indexOf(fileInfo.name); + expectedResultSorted.push(expectedResult[index]); + }); + }) + .expect(200, expectedResultSorted) + .end((err) => { + if (err) return done(err); + uploadedFilesPath.forEach((uploadedFilePath) => { + fs.statSync(uploadedFilePath); + }); + done(); + }); + }); +}); + +describe('Test Upload With Fields', function() { + const app = server.setup(); + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + const uploadedFilePath = path.join(uploadDir, fileName); + // Expected results + const result = genUploadResult(fileName, filePath); + result.firstName = mockUser.firstName; + result.lastName = mockUser.lastName; + result.email = mockUser.email; + + it(`upload ${fileName} and submit fields at the same time with POST`, function(done) { + clearUploadsDir(); + request(app) + .post('/upload/single/withfields') + .attach('testFile', filePath) + .field('firstName', mockUser.firstName) + .field('lastName', mockUser.lastName) + .field('email', mockUser.email) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + + it(`upload ${fileName} and submit fields at the same time with PUT`, function(done) { + clearUploadsDir(); + request(app) + .put('/upload/single/withfields') + .attach('testFile', filePath) + .field('firstName', mockUser.firstName) + .field('lastName', mockUser.lastName) + .field('email', mockUser.email) + .expect(resetBodyUploadData) + .expect(200, result, err => (err ? done(err) : fs.stat(uploadedFilePath, done))); + }); + }); +}); + +describe('Test Aborting/Canceling during upload', function() { + this.timeout(4000); // Set timeout for async tests. + const uploadTimeout = 1000; + + const app = server.setup({ + useTempFiles: true, + tempFileDir: tempDir, + debug: true, + uploadTimeout + }); + + clearTempDir(); + clearUploadsDir(); + mockFiles.forEach((fileName) => { + const filePath = path.join(fileDir, fileName); + + it(`Delete temp file if ${fileName} upload was aborted`, (done) => { + const req = request(app) + .post('/upload/single') + .attach('testFile', filePath) + .on('progress', (e) => { + const progress = (e.loaded * 100) / e.total; + // Aborting request, use req.req since it is original superagent request. + if (progress > 50) req.req.abort(); + }) + .end((err) => { + if (!err) return done(`Connection hasn't been aborted!`); + if (err.code !== 'ECONNRESET') return done(err); + // err.code === 'ECONNRESET' that means upload has been aborted. + // Checking temp directory after upload timeout. + setTimeout(() => { + fs.readdir(tempDir, (err, files) => { + if (err) return done(err); + return files.length ? done(`Temporary directory contains files!`) : done(); + }); + }, uploadTimeout * 2); + }); + }); + }); +}); diff --git a/server/node_modules/express-fileupload/test/options.spec.js b/server/node_modules/express-fileupload/test/options.spec.js new file mode 100644 index 0000000..03dd39d --- /dev/null +++ b/server/node_modules/express-fileupload/test/options.spec.js @@ -0,0 +1,219 @@ +const fs = require('fs'); +const path = require('path'); +const request = require('supertest'); +const server = require('./server'); +const clearUploadsDir = server.clearUploadsDir; +const fileDir = server.fileDir; +const uploadDir = server.uploadDir; + +describe('File Upload Options Tests', function() { + afterEach(function(done) { + clearUploadsDir(); + done(); + }); + + /** + * Upload the file for testing and verify the expected filename. + * @param {object} options The expressFileUpload options. + * @param {string} actualFileNameToUpload The name of the file to upload. + * @param {string} expectedFileNameOnFileSystem The name of the file after upload. + * @param {function} done The mocha continuation function. + */ + function executeFileUploadTestWalk(options, + actualFileNameToUpload, + expectedFileNameOnFileSystem, + done) { + request(server.setup(options)) + .post('/upload/single') + .attach('testFile', path.join(fileDir, actualFileNameToUpload)) + .expect(200) + .end(function(err) { + if (err) { + return done(err); + } + + const uploadedFilePath = path.join(uploadDir, expectedFileNameOnFileSystem); + + fs.stat(uploadedFilePath, done); + }); + } + + describe('Testing [safeFileNames] option to ensure:', function() { + it('Does nothing to your filename when disabled.', + function(done) { + const fileUploadOptions = {safeFileNames: false}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'my$Invalid#fileName.png123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Is disabled by default.', + function(done) { + const fileUploadOptions = null; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'my$Invalid#fileName.png123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Strips away all non-alphanumeric characters (excluding hyphens/underscores) when enabled.', + function(done) { + const fileUploadOptions = {safeFileNames: true}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Accepts a regex for stripping (decidedly) "invalid" characters from filename.', + function(done) { + const fileUploadOptions = {safeFileNames: /[$#]/g}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileName.png123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + }); + + describe('Testing [preserveExtension] option to ensure:', function() { + it('Does not preserve the extension of your filename when disabled.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: false}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Is disabled by default.', + function(done) { + const fileUploadOptions = {safeFileNames: true}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Shortens your extension to the default(3) when enabled, if the extension found is larger.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: true}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng.123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Leaves your extension alone when enabled, if the extension found is <= default(3) length', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: true}; + const actualFileName = 'car.png'; + const expectedFileName = 'car.png'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Can be configured for an extension length > default(3).', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: 7}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileName.png123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Can be configured for an extension length < default(3).', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: 2}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng1.23'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Will use the absolute value of your extension length when negative.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: -5}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamep.ng123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Will leave no extension when the extension length == 0.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: 0}; + const actualFileName = 'car.png'; + const expectedFileName = 'car'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Will accept numbers as strings, if they can be resolved with parseInt.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: '3'}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng.123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Will be evaluated for truthy-ness if it cannot be parsed as an int.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: 'not-a-#-but-truthy'}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng.123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Will ignore any decimal amount when evaluating for extension length.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: 4.98}; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepn.g123'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + + it('Only considers the last dotted part as the extension.', + function(done) { + const fileUploadOptions = {safeFileNames: true, preserveExtension: true}; + const actualFileName = 'basket.ball.bp'; + const expectedFileName = 'basketball.bp'; + + executeFileUploadTestWalk(fileUploadOptions, actualFileName, expectedFileName, done); + }); + }); + + describe('Testing [parseNested] option to ensure:', function() { + it('When [parseNested] is enabled result are nested', function(done){ + const app = server.setup({parseNested: true}); + request(app) + .post('/fields/nested') + .field('name', 'John') + .field('hobbies[0]', 'Cinema') + .field('hobbies[1]', 'Bike') + .expect('Content-Type', /json/) + .expect(200, { + name: 'John', + hobbies: ['Cinema', 'Bike'] + }, done); + }); + + it('When [parseNested] is disabled are flattened', function(done){ + const app = server.setup({parseNested: false}); + request(app) + .post('/fields/flattened') + .field('name', 'John') + .field('hobbies[0]', 'Cinema') + .field('hobbies[1]', 'Bike') + .expect('Content-Type', /json/) + .expect(200, { + name: 'John', + 'hobbies[0]': 'Cinema', + 'hobbies[1]': 'Bike' + }, done); + }); + }); +}); diff --git a/server/node_modules/express-fileupload/test/processNested.spec.js b/server/node_modules/express-fileupload/test/processNested.spec.js new file mode 100644 index 0000000..b9b636d --- /dev/null +++ b/server/node_modules/express-fileupload/test/processNested.spec.js @@ -0,0 +1,48 @@ +'use strict'; + +const assert = require('assert'); +const processNested = require('../lib/processNested'); + +describe('Test Convert Flatten object to Nested object', function() { + it('With no nested data', () => { + const data = { + 'firstname': 'John', + 'lastname': 'Doe', + 'age': 22 + }, + excerpt = { firstname: 'John', lastname: 'Doe', age: 22 }, + processed = processNested(data); + + assert.deepEqual(processed, excerpt); + }); + + it('With nested data', () => { + const data = { + 'firstname': 'John', + 'lastname': 'Doe', + 'age': 22, + 'hobbies[0]': 'Cinema', + 'hobbies[1]': 'Bike', + 'address[line]': '78 Lynch Street', + 'address[city]': 'Milwaukee', + 'friends[0][name]': 'Jane', + 'friends[0][lastname]': 'Doe', + 'friends[1][name]': 'Joe', + 'friends[1][lastname]': 'Doe' + }, + excerpt = { + firstname: 'John', + lastname: 'Doe', + age: 22, + hobbies: [ 'Cinema', 'Bike' ], + address: { line: '78 Lynch Street', city: 'Milwaukee' }, + friends: [ + { name: 'Jane', lastname: 'Doe' }, + { name: 'Joe', lastname: 'Doe' } + ] + }, + processed = processNested(data); + + assert.deepEqual(processed, excerpt); + }); +}); diff --git a/server/node_modules/express-fileupload/test/server.js b/server/node_modules/express-fileupload/test/server.js new file mode 100644 index 0000000..ae399e0 --- /dev/null +++ b/server/node_modules/express-fileupload/test/server.js @@ -0,0 +1,271 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const rimraf = require('rimraf'); + +const fileDir = path.join(__dirname, 'files'); +const tempDir = path.join(__dirname, 'temp'); +const uploadDir = path.join(__dirname, 'uploads'); + +const clearDir = (dir) => { + if (fs.existsSync(dir)) rimraf.sync(dir); + fs.mkdirSync(dir, { recursive: true }); +}; + +const clearUploadsDir = () => clearDir(uploadDir); +const clearTempDir = () => clearDir(tempDir); + +const getUploadedFileData = (file) => ({ + md5: file.md5, + name: file.name, + size: file.size, + uploadPath: path.join(uploadDir, file.name), + uploadDir: uploadDir +}); + +const setup = (fileUploadOptions) => { + const express = require('express'); + const expressFileupload = require('../lib/index'); + + const app = express(); + + app.use(expressFileupload(fileUploadOptions || {})); + + app.all('/upload/single', (req, res) => { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + const testFile = req.files.testFile; + const fileData = getUploadedFileData(testFile); + + testFile.mv(fileData.uploadPath, (err) => { + if (err) { + console.log('ERR', err); // eslint-disable-line + return res.status(500).send(err); + } + res.json(fileData); + }); + }); + + app.all('/upload/single/promise', (req, res) => { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + const testFile = req.files.testFile; + const fileData = getUploadedFileData(testFile); + + testFile + .mv(fileData.uploadPath) + .then(() => { + res.json(fileData); + }) + .catch(err => { + res.status(500).send(err); + }); + }); + + app.all('/upload/single/withfields', (req, res) => { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + if (!req.body) { + return res.status(400).send('No request body found'); + } + + const fields = ['firstName', 'lastName', 'email']; + for (let i = 0; i < fields.length; i += 1) { + if (!req.body[fields[i]] || !req.body[fields[i]].trim()) { + return res.status(400).send(`Invalid field: ${fields[i]}`); + } + } + + const testFile = req.files.testFile; + const fileData = getUploadedFileData(testFile); + fields.forEach((field) => { fileData[field] = req.body[field]; }); + + testFile.mv(fileData.uploadPath, (err) => { + if (err) { + return res.status(500).send(err); + } + res.json(fileData); + }); + }); + + app.all('/upload/single/truncated', (req, res) => { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + // status 400 to differentiate from ending the request in the on limit + return req.files.testFile.truncated + ? res.status(400).send(`File too big`) + : res.status(200).send('Upload succeed'); + }); + + app.all('/upload/multiple', function(req, res) { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + const fileNames = ['testFile1', 'testFile2', 'testFile3']; + + const testFiles = fileNames.map(file => req.files[file]); + for (let i = 0; i < testFiles.length; i += 1) { + if (!testFiles[i]) { + return res.status(400).send(`${fileNames[i]} was not uploaded!`); + } + } + + const filesData = testFiles.map(file => getUploadedFileData(file)); + + testFiles[0].mv(filesData[0].uploadPath, (err) => { + if (err) { + return res.status(500).send(err); + } + + testFiles[1].mv(filesData[1].uploadPath, (err) => { + if (err) { + return res.status(500).send(err); + } + + testFiles[2].mv(filesData[2].uploadPath, (err) => { + if (err) { + return res.status(500).send(err); + } + + res.json(filesData); + }); + }); + }); + }); + + app.all('/upload/array', function(req, res) { + if (!req.files) { + return res.status(400).send('No files were uploaded.'); + } + + const testFiles = req.files.testFiles; + + if (!testFiles) { + return res.status(400).send('No files were uploaded'); + } + + if (!Array.isArray(testFiles)) { + return res.status(400).send('Files were not uploaded as an array'); + } + + if (!testFiles.length) { + return res.status(400).send('Files array is empty'); + } + + const filesData = testFiles.map(file => getUploadedFileData(file)); + + let uploadCount = 0; + for (let i = 0; i < testFiles.length; i += 1) { + + testFiles[i].mv(filesData[i].uploadPath, (err) => { + if (err) { + return res.status(500).send(err); + } + + uploadCount += 1; + if (uploadCount === testFiles.length) { + res.json(filesData); + } + }); + } + }); + + app.all('/fields/user', function(req, res) { + if (!req.body) { + return res.status(400).send('No request body found'); + } + + const fields = ['firstName', 'lastName', 'email']; + for (let i = 0; i < fields.length; i += 1) { + if (!req.body[fields[i]] || !req.body[fields[i]].trim()) { + return res.status(400).send(`Invalid field: ${fields[i]}`); + } + } + + res.json({ + firstName: req.body.firstName, + lastName: req.body.lastName, + email: req.body.email + }); + }); + + app.all('/fields/nested', function(req, res) { + if (!req.body) { + return res.status(400).send('No request body found'); + } + + if (!req.body.name || !req.body.name.trim()) { + return res.status(400).send('Invalid name'); + } + + if (!req.body.hobbies || !req.body.hobbies.length == 2) { + return res.status(400).send('Invalid hobbies'); + } + + res.json({ + name: req.body.name, + hobbies: req.body.hobbies + }); + }); + + app.all('/fields/flattened', function(req, res) { + if (!req.body) { + return res.status(400).send('No request body found'); + } + + if (!req.body.name || !req.body.name.trim()) { + return res.status(400).send('Invalid name'); + } + + if (!req.body['hobbies[0]'] || !req.body['hobbies[0]'].trim()) { + return res.status(400).send('Invalid hobbies[0]'); + } + + if (!req.body['hobbies[1]'] || !req.body['hobbies[1]'].trim()) { + return res.status(400).send('Invalid hobbies[1]'); + } + + res.json({ + name: req.body.name, + 'hobbies[0]': req.body['hobbies[0]'], + 'hobbies[1]': req.body['hobbies[1]'] + }); + }); + + app.all('/fields/array', function(req, res) { + if (!req.body) { + return res.status(400).send('No request body found'); + } + + if (!req.body.testField) { + return res.status(400).send('Invalid field'); + } + + if (!Array.isArray(req.body.testField)) { + return res.status(400).send('Field is not an array'); + } + + res.json(req.body.testField); + }); + + return app; +}; + +module.exports = { + setup, + fileDir, + tempDir, + uploadDir, + clearTempDir, + clearUploadsDir +}; diff --git a/server/node_modules/express-fileupload/test/tempFile.spec.js b/server/node_modules/express-fileupload/test/tempFile.spec.js new file mode 100644 index 0000000..a55931a --- /dev/null +++ b/server/node_modules/express-fileupload/test/tempFile.spec.js @@ -0,0 +1,132 @@ +const fs = require('fs'); +const md5 = require('md5'); +const path = require('path'); +const request = require('supertest'); +const server = require('./server'); +const clearUploadsDir = + server.clearUploadsDir; +const fileDir = + server.fileDir; +const uploadDir = + server.uploadDir; +describe('File Upload Options Tests', function() { + afterEach(function(done) { + clearUploadsDir(); + done(); + }); + /** + * Upload the file for testing and verify the expected filename. + * @param {object} options The expressFileUpload options. + * @param {string} actualFileNameToUpload The name of the file to upload. + * @param {string} expectedFileNameOnFileSystem The name of the file after upload. + * @param {function} done The mocha continuation function. + */ + function executeFileUploadTestWalk( + options, + actualFileNameToUpload, + expectedFileNameOnFileSystem, + done + ) { + + let filePath = path.join(fileDir, actualFileNameToUpload); + let fileBuffer = fs.readFileSync(filePath); + let fileHash = md5(fileBuffer); + let fileStat = fs.statSync(filePath); + let uploadedFilePath = path.join(uploadDir, expectedFileNameOnFileSystem); + + request( + server.setup(options) + ) + .post('/upload/single') + .attach('testFile', filePath) + .expect((res)=>{ + res.body.uploadDir = ''; + res.body.uploadPath = ''; + }) + .expect(200, { + name: expectedFileNameOnFileSystem, + md5: fileHash, + size: fileStat.size, + uploadDir: '', + uploadPath: '' + }) + .end(function(err) { + if (err) { + return done(err); + } + + fs.stat(uploadedFilePath, done); + }); + } + describe('Testing [safeFileNames with useTempFiles] option to ensure:', function() { + it('Does nothing to your filename when disabled.', function(done) { + const fileUploadOptions = { + safeFileNames: false, + useTempFiles: true, + tempFileDir: '/tmp/' + }; + const actualFileName = + 'my$Invalid#fileName.png123'; + const expectedFileName = + 'my$Invalid#fileName.png123'; + executeFileUploadTestWalk( + fileUploadOptions, + actualFileName, + expectedFileName, + done + ); + }); + it('Is disabled by default.', function(done) { + const fileUploadOptions = { + useTempFiles: true, + tempFileDir: '/tmp/' + }; + const actualFileName = + 'my$Invalid#fileName.png123'; + const expectedFileName = + 'my$Invalid#fileName.png123'; + executeFileUploadTestWalk( + fileUploadOptions, + actualFileName, + expectedFileName, + done + ); + }); + + it( + 'Strips away all non-alphanumeric characters (excluding hyphens/underscores) when enabled.', + function(done) { + const fileUploadOptions = { + safeFileNames: true, + useTempFiles: true, + tempFileDir: '/tmp/' + }; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileNamepng123'; + executeFileUploadTestWalk( + fileUploadOptions, + actualFileName, + expectedFileName, + done + ); + }); + + it( + 'Accepts a regex for stripping (decidedly) "invalid" characters from filename.', + function(done) { + const fileUploadOptions = { + safeFileNames: /[$#]/g, + useTempFiles: true, + tempFileDir: '/tmp/' + }; + const actualFileName = 'my$Invalid#fileName.png123'; + const expectedFileName = 'myInvalidfileName.png123'; + executeFileUploadTestWalk( + fileUploadOptions, + actualFileName, + expectedFileName, + done + ); + }); + }); +}); diff --git a/server/node_modules/express-fileupload/test/uploadtimer.spec.js b/server/node_modules/express-fileupload/test/uploadtimer.spec.js new file mode 100644 index 0000000..85c5844 --- /dev/null +++ b/server/node_modules/express-fileupload/test/uploadtimer.spec.js @@ -0,0 +1,28 @@ +'use strict'; + +const assert = require('assert'); +const UploadTimer = require('../lib/uploadtimer'); + +describe('Test UploadTimer class', () => { + + it('It runs a callback function after specified timeout.', (done) => { + const uploadTimer = new UploadTimer(1000, done); + uploadTimer.set(); + }); + + it('set method returns true if timeout specified.', () => { + const uploadTimer = new UploadTimer(1000); + assert.equal(uploadTimer.set(), true); + }); + + it('set method returns false if timeout has not specified.', () => { + const uploadTimer = new UploadTimer(); + assert.equal(uploadTimer.set(), false); + }); + + it('set method returns false if zero timeout has specified.', () => { + const uploadTimer = new UploadTimer(0); + assert.equal(uploadTimer.set(), false); + }); + +}); diff --git a/server/node_modules/express-fileupload/test/utilities.spec.js b/server/node_modules/express-fileupload/test/utilities.spec.js new file mode 100644 index 0000000..338b782 --- /dev/null +++ b/server/node_modules/express-fileupload/test/utilities.spec.js @@ -0,0 +1,403 @@ +'use strict'; + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const md5 = require('md5'); +const server = require('./server'); +const fileDir = server.fileDir; +const uploadDir = server.uploadDir; +const { + debugLog, + isFunc, + errorFunc, + getTempFilename, + buildOptions, + buildFields, + checkAndMakeDir, + deleteFile, + copyFile, + saveBufferToFile, + parseFileName, + uriDecodeFileName +} = require('../lib/utilities'); + +const mockFile = 'basketball.png'; +const mockBuffer = fs.readFileSync(path.join(fileDir, mockFile)); +const mockHash = md5(mockBuffer); + + +describe('Test of the utilities functions', function() { + beforeEach(function() { + server.clearUploadsDir(); + }); + //debugLog tests + describe('Test debugLog function', () => { + + let testMessage = 'Test message'; + + it('debugLog returns false if no options passed', () => { + assert.equal(debugLog(null, testMessage), false); + }); + + it('debugLog returns false if option debug is false', () => { + assert.equal(debugLog({debug: false}, testMessage), false); + }); + + it('debugLog returns true if option debug is true', () => { + assert.equal(debugLog({debug: true}, testMessage), true); + }); + + }); + //isFunc tests + describe('Test isFunc function', () => { + + it('isFunc returns true if function passed', () => assert.equal(isFunc(()=>{}), true)); + + it('isFunc returns false if null passed', function() { + assert.equal(isFunc(null), false); + }); + + it('isFunc returns false if undefined passed', function() { + assert.equal(isFunc(undefined), false); + }); + + it('isFunc returns false if object passed', function() { + assert.equal(isFunc({}), false); + }); + + it('isFunc returns false if array passed', function() { + assert.equal(isFunc([]), false); + }); + }); + //errorFunc tests + describe('Test errorFunc function', () => { + + const resolve = () => 'success'; + const reject = () => 'error'; + + it('errorFunc returns resolve if reject function has not been passed', () => { + let result = errorFunc(resolve); + assert.equal(result(), 'success'); + }); + + it('errorFunc returns reject if reject function has been passed', () => { + let result = errorFunc(resolve, reject); + assert.equal(result(), 'error'); + }); + + }); + //getTempFilename tests + describe('Test getTempFilename function', () => { + + const nameRegexp = /tmp-\d{1,5}-\d{1,}/; + + it('getTempFilename result matches regexp /tmp-d{1,5}-d{1,}/', () => { + + let errCounter = 0; + let tempName = ''; + for (var i = 0; i < 65537; i++) { + tempName = getTempFilename(); + if (!nameRegexp.test(tempName)) errCounter ++; + } + + assert.equal(errCounter, 0); + }); + + it('getTempFilename current and previous results are not equal', () => { + + let errCounter = 0; + let tempName = ''; + let previousName = ''; + for (var i = 0; i < 65537; i++) { + previousName = tempName; + tempName = getTempFilename(); + if (previousName === tempName) errCounter ++; + } + + assert.equal(errCounter, 0); + }); + + }); + //parseFileName + describe('Test parseFileName function', () => { + + it('Does nothing to your filename when disabled.', () => { + const opts = {safeFileNames: false}; + const name = 'my$Invalid#fileName.png123'; + const expected = 'my$Invalid#fileName.png123'; + let result = parseFileName(opts, name); + assert.equal(result, expected); + }); + + it('Cuts of file name length if it more then 255 chars.', () => { + const name = 'a'.repeat(300); + const result = parseFileName({}, name); + assert.equal(result.length, 255); + }); + + it( + 'Strips away all non-alphanumeric characters (excluding hyphens/underscores) when enabled.', + () => { + const opts = {safeFileNames: true}; + const name = 'my$Invalid#fileName.png123'; + const expected = 'myInvalidfileNamepng123'; + let result = parseFileName(opts, name); + assert.equal(result, expected); + }); + + it( + 'Strips away all non-alphanumeric chars when preserveExtension: true for a name without dots', + () => { + const opts = {safeFileNames: true, preserveExtension: true}; + const name = 'my$Invalid#fileName'; + const expected = 'myInvalidfileName'; + let result = parseFileName(opts, name); + assert.equal(result, expected); + }); + + it('Accepts a regex for stripping (decidedly) "invalid" characters from filename.', () => { + const opts = {safeFileNames: /[$#]/g}; + const name = 'my$Invalid#fileName.png123'; + const expected = 'myInvalidfileName.png123'; + let result = parseFileName(opts, name); + assert.equal(result, expected); + }); + + it( + 'Returns correct filename if name contains dots characters and preserveExtension: true.', + () => { + const opts = {safeFileNames: true, preserveExtension: true}; + const name = 'basket.ball.png'; + const expected = 'basketball.png'; + let result = parseFileName(opts, name); + assert.equal(result, expected); + }); + + it('Returns a temporary file name if name argument is empty.', () => { + const opts = {safeFileNames: false}; + const result = parseFileName(opts); + assert.equal(typeof result, 'string'); + }); + + }); + //buildOptions tests + describe('Test buildOptions function', () => { + + const source = { option1: '1', option2: '2' }; + const sourceAddon = { option3: '3'}; + const expected = { option1: '1', option2: '2' }; + const expectedAddon = { option1: '1', option2: '2', option3: '3'}; + + it('buildOptions returns and equal object to the object which was paased', () => { + let result = buildOptions(source); + assert.deepStrictEqual(result, source); + }); + + it('buildOptions doesnt add non object or null arguments to the result', () => { + let result = buildOptions(source, 2, '3', null); + assert.deepStrictEqual(result, expected); + }); + + it('buildOptions adds value to the result from the several source argumets', () => { + let result = buildOptions(source, sourceAddon); + assert.deepStrictEqual(result, expectedAddon); + }); + + }); + //buildFields tests + describe('Test buildOptions function', () => { + + it('buildFields does nothing if null value has been passed', () => { + let fields = null; + fields = buildFields(fields, 'test', null); + assert.equal(fields, null); + }); + + }); + //checkAndMakeDir tests + describe('Test checkAndMakeDir function', () => { + // + it('checkAndMakeDir returns false if upload options object was not set', () => { + assert.equal(checkAndMakeDir(), false); + }); + // + it('checkAndMakeDir returns false if upload option createParentPath was not set', () => { + assert.equal(checkAndMakeDir({}), false); + }); + // + it('checkAndMakeDir returns false if filePath was not set', () => { + assert.equal(checkAndMakeDir({createParentPath: true}), false); + }); + // + it('checkAndMakeDir return true if path to the file already exists', ()=>{ + let dir = path.join(uploadDir, 'testfile'); + assert.equal(checkAndMakeDir({createParentPath: true}, dir), true); + }); + // + it('checkAndMakeDir creates a dir if path to the file not exists', ()=>{ + let dir = path.join(uploadDir, 'testfolder', 'testfile'); + assert.equal(checkAndMakeDir({createParentPath: true}, dir), true); + }); + // + it('checkAndMakeDir creates a dir recursively if path to the file not exists', ()=>{ + let dir = path.join(uploadDir, 'testfolder', 'testsubfolder', 'testfile'); + assert.equal(checkAndMakeDir({createParentPath: true}, dir), true); + }); + }); + //saveBufferToFile tests + describe('Test saveBufferToFile function', function(){ + beforeEach(function() { + server.clearUploadsDir(); + }); + + it('Save buffer to a file', function(done) { + let filePath = path.join(uploadDir, mockFile); + saveBufferToFile(mockBuffer, filePath, function(err){ + if (err) { + return done(err); + } + fs.stat(filePath, done); + }); + }); + + it('Failed if not a buffer passed', function(done) { + let filePath = path.join(uploadDir, mockFile); + saveBufferToFile(undefined, filePath, function(err){ + if (err) { + return done(); + } + }); + }); + + it('Failed if wrong path passed', function(done) { + let filePath = ''; + saveBufferToFile(mockFile, filePath, function(err){ + if (err) { + return done(); + } + }); + }); + }); + + describe('Test deleteFile function', function(){ + beforeEach(function() { + server.clearUploadsDir(); + }); + + it('Failed if nonexistent file passed', function(done){ + let filePath = path.join(uploadDir, getTempFilename()); + + deleteFile(filePath, function(err){ + if (err) { + return done(); + } + }); + }); + + it('Delete a file', function(done){ + let srcPath = path.join(fileDir, mockFile); + let dstPath = path.join(uploadDir, getTempFilename()); + + //copy a file + copyFile(srcPath, dstPath, function(err){ + if (err) { + return done(err); + } + fs.stat(dstPath, (err)=>{ + if (err){ + return done(err); + } + // delete a file + deleteFile(dstPath, function(err){ + if (err) { + return done(err); + } + + fs.stat(dstPath, (err)=>{ + if (err){ + return done(); + } + + //error if a file still exist + done(err); + }); + }); + }); + }); + }); + + }); + + describe('Test copyFile function', function(){ + beforeEach(function() { + server.clearUploadsDir(); + }); + + it('Copy a file and check a hash', function(done) { + let srcPath = path.join(fileDir, mockFile); + let dstPath = path.join(uploadDir, mockFile); + + copyFile(srcPath, dstPath, function(err){ + if (err) { + return done(err); + } + fs.stat(dstPath, (err)=>{ + if (err){ + return done(err); + } + //Match source and destination files hash. + let fileBuffer = fs.readFileSync(dstPath); + let fileHash = md5(fileBuffer); + return (fileHash === mockHash) ? done() : done(err); + }); + }); + }); + + it('Failed if wrong source file path passed', function(done){ + let srcPath = path.join(fileDir, 'unknown'); + let dstPath = path.join(uploadDir, mockFile); + + copyFile(srcPath, dstPath, function(err){ + if (err) { + return done(); + } + }); + }); + + it('Failed if wrong destination file path passed', function(done){ + let srcPath = path.join(fileDir, 'unknown'); + let dstPath = path.join('unknown', 'unknown'); + + copyFile(srcPath, dstPath, function(err){ + if (err) { + return done(); + } + }); + }); + }); + + describe('Test uriDecodeFileName function', function() { + const testData = [ + { enc: 'test%22filename', dec: 'test"filename' }, + { enc: 'test%60filename', dec: 'test`filename' }, + { enc: '%3Fx%3Dtest%22filename', dec: '?x=test"filename'} + ]; + + // Test decoding if uriDecodeFileNames: true. + testData.forEach((testName) => { + const opts = { uriDecodeFileNames: true }; + it(`Return ${testName.dec} for input ${testName.enc} if uriDecodeFileNames: true`, () => { + assert.equal(uriDecodeFileName(opts, testName.enc), testName.dec); + }); + }); + + // Test decoding if uriDecodeFileNames: false. + testData.forEach((testName) => { + const opts = { uriDecodeFileNames: false }; + it(`Return ${testName.enc} for input ${testName.enc} if uriDecodeFileNames: false`, () => { + assert.equal(uriDecodeFileName(opts, testName.enc), testName.enc); + }); + }); + }); +}); diff --git a/server/node_modules/streamsearch/LICENSE b/server/node_modules/streamsearch/LICENSE new file mode 100644 index 0000000..290762e --- /dev/null +++ b/server/node_modules/streamsearch/LICENSE @@ -0,0 +1,19 @@ +Copyright Brian White. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. \ No newline at end of file diff --git a/server/node_modules/streamsearch/README.md b/server/node_modules/streamsearch/README.md new file mode 100644 index 0000000..6310c20 --- /dev/null +++ b/server/node_modules/streamsearch/README.md @@ -0,0 +1,87 @@ +Description +=========== + +streamsearch is a module for [node.js](http://nodejs.org/) that allows searching a stream using the Boyer-Moore-Horspool algorithm. + +This module is based heavily on the Streaming Boyer-Moore-Horspool C++ implementation by Hongli Lai [here](https://github.com/FooBarWidget/boyer-moore-horspool). + + +Requirements +============ + +* [node.js](http://nodejs.org/) -- v0.8.0 or newer + + +Installation +============ + + npm install streamsearch + +Example +======= + +```javascript + var StreamSearch = require('streamsearch'), + inspect = require('util').inspect; + + var needle = new Buffer([13, 10]), // CRLF + s = new StreamSearch(needle), + chunks = [ + new Buffer('foo'), + new Buffer(' bar'), + new Buffer('\r'), + new Buffer('\n'), + new Buffer('baz, hello\r'), + new Buffer('\n world.'), + new Buffer('\r\n Node.JS rules!!\r\n\r\n') + ]; + s.on('info', function(isMatch, data, start, end) { + if (data) + console.log('data: ' + inspect(data.toString('ascii', start, end))); + if (isMatch) + console.log('match!'); + }); + for (var i = 0, len = chunks.length; i < len; ++i) + s.push(chunks[i]); + + // output: + // + // data: 'foo' + // data: ' bar' + // match! + // data: 'baz, hello' + // match! + // data: ' world.' + // match! + // data: ' Node.JS rules!!' + // match! + // data: '' + // match! +``` + + +API +=== + +Events +------ + +* **info**(< _boolean_ >isMatch[, < _Buffer_ >chunk, < _integer_ >start, < _integer_ >end]) - A match _may_ or _may not_ have been made. In either case, a preceding `chunk` of data _may_ be available that did not match the needle. Data (if available) is in `chunk` between `start` (inclusive) and `end` (exclusive). + + +Properties +---------- + +* **maxMatches** - < _integer_ > - The maximum number of matches. Defaults to Infinity. + +* **matches** - < _integer_ > - The current match count. + + +Functions +--------- + +* **(constructor)**(< _mixed_ >needle) - Creates and returns a new instance for searching for a _Buffer_ or _string_ `needle`. + +* **push**(< _Buffer_ >chunk) - _integer_ - Processes `chunk`. The return value is the last processed index in `chunk` + 1. + +* **reset**() - _(void)_ - Resets internal state. Useful for when you wish to start searching a new/different stream for example. diff --git a/server/node_modules/streamsearch/lib/sbmh.js b/server/node_modules/streamsearch/lib/sbmh.js new file mode 100644 index 0000000..dbefbc1 --- /dev/null +++ b/server/node_modules/streamsearch/lib/sbmh.js @@ -0,0 +1,213 @@ +/* + Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation + by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool +*/ +var EventEmitter = require('events').EventEmitter, + inherits = require('util').inherits; + +function jsmemcmp(buf1, pos1, buf2, pos2, num) { + for (var i = 0; i < num; ++i, ++pos1, ++pos2) + if (buf1[pos1] !== buf2[pos2]) + return false; + return true; +} + +function SBMH(needle) { + if (typeof needle === 'string') + needle = new Buffer(needle); + var i, j, needle_len = needle.length; + + this.maxMatches = Infinity; + this.matches = 0; + + this._occ = new Array(256); + this._lookbehind_size = 0; + this._needle = needle; + this._bufpos = 0; + + this._lookbehind = new Buffer(needle_len); + + // Initialize occurrence table. + for (j = 0; j < 256; ++j) + this._occ[j] = needle_len; + + // Populate occurrence table with analysis of the needle, + // ignoring last letter. + if (needle_len >= 1) { + for (i = 0; i < needle_len - 1; ++i) + this._occ[needle[i]] = needle_len - 1 - i; + } +} +inherits(SBMH, EventEmitter); + +SBMH.prototype.reset = function() { + this._lookbehind_size = 0; + this.matches = 0; + this._bufpos = 0; +}; + +SBMH.prototype.push = function(chunk, pos) { + var r, chlen; + if (!Buffer.isBuffer(chunk)) + chunk = new Buffer(chunk, 'binary'); + chlen = chunk.length; + this._bufpos = pos || 0; + while (r !== chlen && this.matches < this.maxMatches) + r = this._sbmh_feed(chunk); + return r; +}; + +SBMH.prototype._sbmh_feed = function(data) { + var len = data.length, needle = this._needle, needle_len = needle.length; + + // Positive: points to a position in `data` + // pos == 3 points to data[3] + // Negative: points to a position in the lookbehind buffer + // pos == -2 points to lookbehind[lookbehind_size - 2] + var pos = -this._lookbehind_size, + last_needle_char = needle[needle_len - 1], + occ = this._occ, + lookbehind = this._lookbehind; + + if (pos < 0) { + // Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool + // search with character lookup code that considers both the + // lookbehind buffer and the current round's haystack data. + // + // Loop until + // there is a match. + // or until + // we've moved past the position that requires the + // lookbehind buffer. In this case we switch to the + // optimized loop. + // or until + // the character to look at lies outside the haystack. + while (pos < 0 && pos <= len - needle_len) { + var ch = this._sbmh_lookup_char(data, pos + needle_len - 1); + + if (ch === last_needle_char + && this._sbmh_memcmp(data, pos, needle_len - 1)) { + this._lookbehind_size = 0; + ++this.matches; + if (pos > -this._lookbehind_size) + this.emit('info', true, lookbehind, 0, this._lookbehind_size + pos); + else + this.emit('info', true); + + this._bufpos = pos + needle_len; + return pos + needle_len; + } else + pos += occ[ch]; + } + + // No match. + + if (pos < 0) { + // There's too few data for Boyer-Moore-Horspool to run, + // so let's use a different algorithm to skip as much as + // we can. + // Forward pos until + // the trailing part of lookbehind + data + // looks like the beginning of the needle + // or until + // pos == 0 + while (pos < 0 && !this._sbmh_memcmp(data, pos, len - pos)) + pos++; + } + + if (pos >= 0) { + // Discard lookbehind buffer. + this.emit('info', false, lookbehind, 0, this._lookbehind_size); + this._lookbehind_size = 0; + } else { + // Cut off part of the lookbehind buffer that has + // been processed and append the entire haystack + // into it. + var bytesToCutOff = this._lookbehind_size + pos; + + if (bytesToCutOff > 0) { + // The cut off data is guaranteed not to contain the needle. + this.emit('info', false, lookbehind, 0, bytesToCutOff); + } + + lookbehind.copy(lookbehind, 0, bytesToCutOff, + this._lookbehind_size - bytesToCutOff); + this._lookbehind_size -= bytesToCutOff; + + data.copy(lookbehind, this._lookbehind_size); + this._lookbehind_size += len; + + this._bufpos = len; + return len; + } + } + + if (pos >= 0) + pos += this._bufpos; + + // Lookbehind buffer is now empty. Perform Boyer-Moore-Horspool + // search with optimized character lookup code that only considers + // the current round's haystack data. + while (pos <= len - needle_len) { + var ch = data[pos + needle_len - 1]; + + if (ch === last_needle_char + && data[pos] === needle[0] + && jsmemcmp(needle, 0, data, pos, needle_len - 1)) { + ++this.matches; + if (pos > 0) + this.emit('info', true, data, this._bufpos, pos); + else + this.emit('info', true); + + this._bufpos = pos + needle_len; + return pos + needle_len; + } else + pos += occ[ch]; + } + + // There was no match. If there's trailing haystack data that we cannot + // match yet using the Boyer-Moore-Horspool algorithm (because the trailing + // data is less than the needle size) then match using a modified + // algorithm that starts matching from the beginning instead of the end. + // Whatever trailing data is left after running this algorithm is added to + // the lookbehind buffer. + if (pos < len) { + while (pos < len && (data[pos] !== needle[0] + || !jsmemcmp(data, pos, needle, 0, len - pos))) { + ++pos; + } + if (pos < len) { + data.copy(lookbehind, 0, pos, pos + (len - pos)); + this._lookbehind_size = len - pos; + } + } + + // Everything until pos is guaranteed not to contain needle data. + if (pos > 0) + this.emit('info', false, data, this._bufpos, pos < len ? pos : len); + + this._bufpos = len; + return len; +}; + +SBMH.prototype._sbmh_lookup_char = function(data, pos) { + if (pos < 0) + return this._lookbehind[this._lookbehind_size + pos]; + else + return data[pos]; +} + +SBMH.prototype._sbmh_memcmp = function(data, pos, len) { + var i = 0; + + while (i < len) { + if (this._sbmh_lookup_char(data, pos + i) === this._needle[i]) + ++i; + else + return false; + } + return true; +} + +module.exports = SBMH; diff --git a/server/node_modules/streamsearch/package.json b/server/node_modules/streamsearch/package.json new file mode 100644 index 0000000..05ece4a --- /dev/null +++ b/server/node_modules/streamsearch/package.json @@ -0,0 +1,59 @@ +{ + "_from": "streamsearch@0.1.2", + "_id": "streamsearch@0.1.2", + "_inBundle": false, + "_integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "_location": "/streamsearch", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "streamsearch@0.1.2", + "name": "streamsearch", + "escapedName": "streamsearch", + "rawSpec": "0.1.2", + "saveSpec": null, + "fetchSpec": "0.1.2" + }, + "_requiredBy": [ + "/dicer" + ], + "_resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "_shasum": "808b9d0e56fc273d809ba57338e929919a1a9f1a", + "_spec": "streamsearch@0.1.2", + "_where": "/home/sigonasr2/divar/server/node_modules/dicer", + "author": { + "name": "Brian White", + "email": "mscdex@mscdex.net" + }, + "bugs": { + "url": "https://github.com/mscdex/streamsearch/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "Streaming Boyer-Moore-Horspool searching for node.js", + "engines": { + "node": ">=0.8.0" + }, + "homepage": "https://github.com/mscdex/streamsearch#readme", + "keywords": [ + "stream", + "horspool", + "boyer-moore-horspool", + "boyer-moore", + "search" + ], + "licenses": [ + { + "type": "MIT", + "url": "http://github.com/mscdex/streamsearch/raw/master/LICENSE" + } + ], + "main": "./lib/sbmh", + "name": "streamsearch", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/mscdex/streamsearch.git" + }, + "version": "0.1.2" +} diff --git a/server/package-lock.json b/server/package-lock.json index 45eef56..609672e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -170,6 +170,14 @@ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" }, + "busboy": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", + "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "requires": { + "dicer": "0.3.0" + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -324,6 +332,14 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", @@ -400,6 +416,14 @@ "vary": "~1.1.2" } }, + "express-fileupload": { + "version": "1.1.7-alpha.4", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.7-alpha.4.tgz", + "integrity": "sha512-uNl/TB3adUH25cDRp1gDoXQ38SdIZXOAVzC54G/xnOAa4M3maBWiZTVz39cnoQ7TXhmYXYpnOfMDMbqSelXFmQ==", + "requires": { + "busboy": "^0.3.1" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1329,6 +1353,11 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-to-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz", diff --git a/server/package.json b/server/package.json index 6331c62..d40a97d 100644 --- a/server/package.json +++ b/server/package.json @@ -12,6 +12,7 @@ "axios": "^0.19.2", "crypto": "^1.0.1", "express": "^4.17.1", + "express-fileupload": "^1.1.7-alpha.4", "get-pixels": "^3.3.2", "image-pixels": "^2.2.2", "pg": "^8.2.1",