This commit is contained in:
2025-10-21 08:10:15 +00:00
commit 8eaf13f445
1983 changed files with 292430 additions and 0 deletions

View File

@@ -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('fileFactory: 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();
});
});
});
});

View File

@@ -0,0 +1,99 @@
'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('fileLimitUloads: 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((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
});
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) {
// err.code === 'ECONNRESET' that means upload has been aborted.
if (err && err.code !== 'ECONNRESET') 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();
});
});
});
});

View File

@@ -0,0 +1,128 @@
'use strict';
const assert = require('assert');
const isEligibleRequest = require('../lib/isEligibleRequest');
describe('isEligibleRequest function tests', () => {
it('should return true if the request method is POST & headers set', () => {
const req = {
method: 'POST',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, true);
});
it('should return true if the request method is PUT & headers set', () => {
const req = {
method: 'PUT',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, true);
});
it('should return true if the request method is PATCH & headers set', () => {
const req = {
method: 'PATCH',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, true);
});
it('should return false if the request method is POST & content length 0', () => {
const req = {
method: 'POST',
headers: {
'content-length': '0',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request method is POST & no content-length', () => {
const req = {
method: 'POST',
headers: {
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request method is POST & no boundary', () => {
const req = {
method: 'POST',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; ----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request method is not POST, PUT or PATCH', () => {
const req = {
method: 'GET',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request method is not POST, PUT or PATCH', () => {
const req = {
method: 'DELETE',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request method is not POST, PUT or PATCH', () => {
const req = {
method: 'OPTIONS',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request method is not POST, PUT or PATCH', () => {
const req = {
method: 'HEAD',
headers: {
'content-length': '768751',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryz2D47BnVMA7w5N36'
}
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
it('should return false if the request is empty or not provided', () => {
const result = isEligibleRequest();
assert.equal(result, false);
});
it('should return false if content-type is not specified.', () => {
const req = {
method: 'POST',
headers: { 'content-length': '768751' }
};
const result = isEligibleRequest(req);
assert.equal(result, false);
});
});

View File

@@ -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('multipartFields: 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('multipartFields: 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.');
});
});
});

View File

@@ -0,0 +1,475 @@
'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('multipartUploads: 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('multipartUploads: 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((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
it('fail when using HEAD', function(done) {
request(app)
.head('/upload/single')
.attach('testFile', path.join(fileDir, mockFiles[0]))
.expect(400)
.end((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
});
describe('multipartUploads: 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('multipartUploads: Test Single File Upload w/ 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((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
it('fail when using HEAD', function(done) {
request(app)
.head('/upload/single')
.attach('testFile', path.join(fileDir, mockFiles[0]))
.expect(400)
.end((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
});
describe('multipartUploads: Single File Upload w/ useTempFiles & 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('multipartUploads: 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((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
it('fail when using HEAD', function(done) {
request(app)
.head('/upload/single')
.attach('testFile', path.join(fileDir, mockFiles[0]))
.expect(400)
.end((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
});
describe('multipartUploads: Test Single File Upload w/ .mv() Promise & useTempFiles', 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((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
it('fail when using HEAD', (done) => {
request(app)
.head('/upload/single')
.attach('testFile', path.join(fileDir, mockFiles[0]))
.expect(400)
.end((err) => {
// err.code === 'ECONNRESET' that means upload has been aborted.
done(err && err.code !== 'ECONNRESET' ? err : null);
});
});
});
describe('multipartUploads: 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('multipartUploads: 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('multipartUploads: 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('multipartUploads: 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);
});
});
});
});

219
node_modules/express-fileupload/test/options.spec.js generated vendored Normal file
View File

@@ -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('options: 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);
});
});
});

5
node_modules/express-fileupload/test/posttests.js generated vendored Normal file
View File

@@ -0,0 +1,5 @@
const { clearFileDir } = require('./server');
console.log('clearing test files...');
clearFileDir();
console.log('done');

5
node_modules/express-fileupload/test/pretests.js generated vendored Normal file
View File

@@ -0,0 +1,5 @@
const { createTestFiles } = require('./server');
console.log('creating test files...');
createTestFiles()
.then(() => console.log('done'));

View File

@@ -0,0 +1,59 @@
'use strict';
const assert = require('assert');
const processNested = require('../lib/processNested');
describe('processNested: 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);
});
it('Do not allow prototype pollution', () => {
const pollutionOb1 = JSON.parse(`{"__proto__.POLLUTED1": "FOOBAR"}`);
const pollutionOb2 = JSON.parse(`{"constructor.prototype.POLLUTED2": "FOOBAR"}`);
processNested(pollutionOb1);
processNested(pollutionOb2);
assert.equal(global.POLLUTED1, undefined);
assert.equal(global.POLLUTED2, undefined);
});
});

295
node_modules/express-fileupload/test/server.js generated vendored Normal file
View File

@@ -0,0 +1,295 @@
'use strict';
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const randomFile = require('rnd-file');
const fileDir = path.join(__dirname, 'files');
const tempDir = path.join(__dirname, 'temp');
const uploadDir = path.join(__dirname, 'uploads');
const mockFiles = [
{ name: 'emptyfile.txt', size: 0 },
{ name: 'basket.ball.bp', size: 151 * 1024 },
{ name: 'basketball.png', size: 151 * 1024 },
{ name: 'car.png', size: 263 * 1024 },
{ name: 'my$Invalid#fileName.png123', size: 263 * 1024 },
{ name: 'tree.png', size: 266 * 1024 }
];
const clearDir = (dir) => {
try {
try {
const stats = fs.statSync(dir);
if (stats.isDirectory()) rimraf.sync(dir);
} catch (statsErr) {
if (statsErr.code !== 'ENOENT') {
throw statsErr;
}
}
fs.mkdirSync(dir, { recursive: true });
} catch (err) {
console.error(err); // eslint-disable-line
}
};
const createTestFiles = () => Promise.all(mockFiles.map((file) => {
return randomFile({ filePath: fileDir, fileName: file.name, fileSize: file.size });
}));
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,
clearFileDir: () => clearDir(fileDir),
clearTempDir: () => clearDir(tempDir),
clearUploadsDir: () => clearDir(uploadDir),
createTestFiles
};

189
node_modules/express-fileupload/test/tempFile.spec.js generated vendored Normal file
View File

@@ -0,0 +1,189 @@
const fs = require('fs');
const md5 = require('md5');
const path = require('path');
const request = require('supertest');
const server = require('./server');
const assert = require('assert');
const process = require('process');
const clearUploadsDir = server.clearUploadsDir;
const fileDir = server.fileDir;
const uploadDir = server.uploadDir;
const tempFileDir = '/tmp/';
/** Default permissions different for win32 and linux systems */
const defaultFilePermissions = process.platform === 'win32' ? 0o666 : 0o644;
describe('tempFile: Test fileupload w/ useTempFiles.', 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,
expectedFilePermissions = defaultFilePermissions
) {
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, function(err, stats) {
if (err) {
return done(err);
}
assert.equal(stats.mode & 0o777, expectedFilePermissions);
done();
});
});
}
describe('Testing [safeFileNames w/ useTempFiles] option to ensure:', function() {
it('Does nothing to your filename when disabled.', function(done) {
const fileUploadOptions = {
safeFileNames: false,
useTempFiles: true,
tempFileDir
};
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
};
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
};
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
};
const actualFileName = 'my$Invalid#fileName.png123';
const expectedFileName = 'myInvalidfileName.png123';
executeFileUploadTestWalk(
fileUploadOptions,
actualFileName,
expectedFileName,
done
);
});
});
describe('Testing [tempFilePermissions w/ useTempFiles] option to ensure:', function() {
it('Does nothing to your filename when disabled.', function(done) {
const fileUploadOptions = {
safeFileNames: false,
useTempFiles: true,
tempFileDir,
tempFilePermissions: 0o666
};
const actualFileName = 'my$Invalid#fileName.png123';
const expectedFileName = 'my$Invalid#fileName.png123';
executeFileUploadTestWalk(
fileUploadOptions,
actualFileName,
expectedFileName,
done
);
});
it('Respects option boundaries when provided.', function(done) {
const fileUploadOptions = {
useTempFiles: true,
tempFileDir,
tempFilePermissions: 0o7777
};
const expressFileupload = require('../lib/index');
assert.throws(function() {
expressFileupload(fileUploadOptions);
}, Error, 'File permissions out of bounds');
done();
});
it('Applies permissions in filesystem.', function(done) {
const fileUploadOptions = {
useTempFiles: true,
tempFileDir,
tempFilePermissions: 0o600
};
const actualFileName = 'my$Invalid#fileName.png123';
const expectedFileName = 'my$Invalid#fileName.png123';
// Expected file perms after moving it to the destination folder depends on the platform.
const expectedFilePermissions = process.platform === 'win32' ? 0o666 : 0o600;
executeFileUploadTestWalk(
fileUploadOptions,
actualFileName,
expectedFileName,
done,
expectedFilePermissions
);
});
});
});

View File

@@ -0,0 +1,41 @@
'use strict';
const assert = require('assert');
const UploadTimer = require('../lib/uploadtimer');
describe('uploadTimer: Test UploadTimer class', () => {
it('It runs a callback function after specified timeout.', (done) => {
const uploadTimer = new UploadTimer(500, done);
uploadTimer.set();
});
it('set method returns true if timeout specified.', () => {
const uploadTimer = new UploadTimer(500);
assert.equal(uploadTimer.set(), true);
uploadTimer.clear();
});
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);
});
it('set method returns false if timer allready set.', () => {
const uploadTimer = new UploadTimer(500);
uploadTimer.set();
assert.equal(uploadTimer.set(), false);
uploadTimer.clear();
});
it('refresh method returns false if timer has not been set/initialized.', () => {
const uploadTimer = new UploadTimer(500);
assert.equal(uploadTimer.refresh(), false);
});
});

510
node_modules/express-fileupload/test/utilities.spec.js generated vendored Normal file
View File

@@ -0,0 +1,510 @@
'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,
moveFile,
saveBufferToFile,
parseFileName,
uriDecodeFileName,
isSafeFromPollution
} = require('../lib/utilities');
const mockFile = 'basketball.png';
const mockBuffer = fs.readFileSync(path.join(fileDir, mockFile));
const mockHash = md5(mockBuffer);
const mockFileMove = 'car.png';
const mockBufferMove = fs.readFileSync(path.join(fileDir, mockFileMove));
const mockHashMove = md5(mockBufferMove);
describe('utilities: Test of the utilities functions', function() {
//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 or logger is not set', () => {
assert.equal(debugLog({debug: false}, testMessage), false);
assert.equal(debugLog({debug: true, logger: undefined}, testMessage), false);
assert.equal(debugLog({debug: true, logger: {}}, testMessage), false);
});
it('debugLog returns true if option debug is true and logger is set', () => {
assert.equal(debugLog({debug: true, logger: console}, testMessage), true);
});
it('supports a custom logger', () => {
const calls = [];
const logger = {
log: (...args) => calls.push(args)
};
debugLog({debug: true, logger}, testMessage);
assert.equal(calls.length, 1);
assert.deepEqual(calls[0], [`Express-file-upload: ${testMessage}`]);
});
});
//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', hashAlgorithm: 'md5' };
const sourceAddon = { option3: '3', hashAlgorithm: 'sha256'};
const expected = { option1: '1', option2: '2', hashAlgorithm: 'md5' };
const expectedAddon = { option1: '1', option2: '2', option3: '3', hashAlgorithm: 'sha256'};
it('buildOptions returns an equal object to the object which was passed', () => {
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 arguments', () => {
let result = buildOptions(source, sourceAddon);
assert.deepStrictEqual(result, expectedAddon);
});
it('buildOptions throws an error when not provided a supported hashAlgorithm', () => {
assert.throws(() => buildOptions({}));
});
it('buildOptions throws an error when given an unsupported hashAlgorithm', () => {
assert.throws(() => buildOptions({ hashAlgorithm: 'not-actual-algo' }));
});
});
//buildFields tests
describe('Test buildFields 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);
});
});
describe('Test moveFile function', function() {
beforeEach(() => server.clearUploadsDir());
it('Should rename a file and check a hash', function(done) {
const srcPath = path.join(fileDir, mockFileMove);
const dstPath = path.join(uploadDir, mockFileMove);
moveFile(srcPath, dstPath, function(err, renamed) {
if (err) {
return done(err);
}
fs.stat(dstPath, (err) => {
if (err) {
return done(err);
}
// Match source and destination files hash.
const fileBuffer = fs.readFileSync(dstPath);
const fileHash = md5(fileBuffer);
if (fileHash !== mockHashMove) {
return done(new Error('Hashes do not match'));
}
// Check that source file was deleted.
fs.stat(srcPath, (err) => {
if (err) {
return done(renamed ? null : new Error('Source file was not renamed'));
}
done(new Error('Source file was not deleted'));
});
});
});
});
});
//saveBufferToFile tests
describe('Test saveBufferToFile function', function(){
beforeEach(() => 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(() => 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)=> {
return err ? done() : done(new Error('File was not deleted'));
});
});
});
});
});
});
describe('Test copyFile function', function() {
beforeEach(() => 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(new Error('Hashes do not match'));
});
});
});
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'},
{ enc: 'bug_bounty_upload_%91%91and%92.txt', dec: 'bug_bounty_upload_and.txt'}
];
// 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);
});
});
});
describe('Test for no prototype pollution in buildFields', function() {
const prototypeFields = [
{ name: '__proto__', data: {} },
{ name: 'constructor', data: {} },
{ name: 'toString', data: {} }
];
const nonPrototypeFields = [
{ name: 'a', data: {} },
{ name: 'b', data: {} }
];
let fieldObject = undefined;
[...prototypeFields, ...nonPrototypeFields].forEach((field) => {
fieldObject = buildFields(fieldObject, field.name, field.data);
});
it(`Has ${nonPrototypeFields.length} keys`, () => {
assert.equal(Object.keys(fieldObject).length, nonPrototypeFields.length);
});
it(`Has null as its prototype`, () => {
assert.equal(Object.getPrototypeOf(fieldObject), null);
});
prototypeFields.forEach((field) => {
it(`${field.name} property is not an array`, () => {
// Note, Array.isArray is an insufficient test due to it returning false
// for Objects with an array prototype.
assert.equal(fieldObject[field.name] instanceof Array, false);
});
});
});
describe('Test for correct detection of prototype pollution', function() {
const validInsertions = [
{ base: {}, key: 'a' },
{ base: { a: 1 }, key: 'a' },
{ base: { __proto__: { a: 1 } }, key: 'a' },
{ base: [1], key: 0 },
{ base: { __proto__: [1] }, key: 0 }
];
const invalidInsertions = [
{ base: {}, key: '__proto__' },
{ base: {}, key: 'constructor' },
{ base: [1], key: '__proto__' },
{ base: [1], key: 'length' },
{ base: { __proto__: [1] }, key: 'length' }
];
validInsertions.forEach((insertion) => {
it(`Key ${insertion.key} should be valid for ${JSON.stringify(insertion.base)}`, () => {
assert.equal(isSafeFromPollution(insertion.base, insertion.key), true);
});
});
invalidInsertions.forEach((insertion) => {
it(`Key ${insertion.key} should not be valid for ${JSON.stringify(insertion.base)}`, () => {
assert.equal(isSafeFromPollution(insertion.base, insertion.key), false);
});
});
});
});