123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- '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,
- isSafeFromPollution
- } = 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);
- });
- });
- });
- 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);
- });
- });
- });
- });
|