From aa94dafac16810ab6763dc6271e8e79747adbd7a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 18 Feb 2015 21:09:33 -0500 Subject: [PATCH] closes #725 --- package.json | 2 + public/language/en_GB/error.json | 2 +- public/src/modules/composer/uploads.js | 3 +- public/src/utils.js | 65 +++++++++++++------------- src/controllers/accounts.js | 34 ++++++-------- src/controllers/uploads.js | 63 +++++++++++++++---------- src/file.js | 43 ++++++++++++++++- src/views/admin/settings/post.tpl | 5 +- 8 files changed, 133 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index b6a70902e4..8561984d0d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "heapdump": "^0.3.0", "less": "^2.0.0", "logrotate-stream": "^0.2.3", + "mime": "^1.3.4", "mkdirp": "~0.5.0", + "mmmagic": "^0.3.13", "morgan": "^1.3.2", "nconf": "~0.7.1", "nodebb-plugin-dbsearch": "^0.1.0", diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index bf4e7229fd..8c2fadce8d 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -64,6 +64,7 @@ "invalid-image-type": "Invalid image type. Allowed types are: %1", "invalid-image-extension": "Invalid image extension", + "invalid-file-type": "Invalid file type. Allowed types are: %1", "group-name-too-short": "Group name too short", "group-already-exists": "Group already exists", @@ -80,7 +81,6 @@ "topic-thumbnails-are-disabled": "Topic thumbnails are disabled.", "invalid-file": "Invalid File", "uploads-are-disabled": "Uploads are disabled", - "upload-error": "Upload Error : %1", "signature-too-long" : "Sorry, your signature cannot be longer than %1 characters.", diff --git a/public/src/modules/composer/uploads.js b/public/src/modules/composer/uploads.js index e7bc473cb5..2a6833c604 100644 --- a/public/src/modules/composer/uploads.js +++ b/public/src/modules/composer/uploads.js @@ -316,8 +316,7 @@ define('composer/uploads', ['composer/preview', 'csrf'], function(preview, csrf) function onUploadError(xhr) { xhr = maybeParse(xhr); - - app.alertError('[[error:upload-error, ' + xhr.responseText + ']]'); + app.alertError(xhr.responseText); } return uploads; diff --git a/public/src/utils.js b/public/src/utils.js index e0fef6151b..9c46e3fb6a 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -130,38 +130,39 @@ return ('' + path).split('.').pop(); }, - fileMimeType: (function () { - // we only care about images, for now - var map = { - "bmp": "image/bmp", - "cmx": "image/x-cmx", - "cod": "image/cis-cod", - "gif": "image/gif", - "ico": "image/x-icon", - "ief": "image/ief", - "jfif": "image/pipeg", - "jpe": "image/jpeg", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "pbm": "image/x-portable-bitmap", - "pgm": "image/x-portable-graymap", - "pnm": "image/x-portable-anymap", - "ppm": "image/x-portable-pixmap", - "ras": "image/x-cmu-raster", - "rgb": "image/x-rgb", - "svg": "image/svg+xml", - "tif": "image/tiff", - "tiff": "image/tiff", - "xbm": "image/x-xbitmap", - "xpm": "image/x-xpixmap", - "xwd": "image/x-xwindowdump" - }; - - return function (path) { - var extension = utils.fileExtension(path); - return map[extension] || '*'; - }; - })(), + extensionMimeTypeMap: { + "bmp": "image/bmp", + "cmx": "image/x-cmx", + "cod": "image/cis-cod", + "gif": "image/gif", + "ico": "image/x-icon", + "ief": "image/ief", + "jfif": "image/pipeg", + "jpe": "image/jpeg", + "jpeg": "image/jpeg", + "jpg": "image/jpeg", + "png": "image/png", + "pbm": "image/x-portable-bitmap", + "pgm": "image/x-portable-graymap", + "pnm": "image/x-portable-anymap", + "ppm": "image/x-portable-pixmap", + "ras": "image/x-cmu-raster", + "rgb": "image/x-rgb", + "svg": "image/svg+xml", + "tif": "image/tiff", + "tiff": "image/tiff", + "xbm": "image/x-xbitmap", + "xpm": "image/x-xpixmap", + "xwd": "image/x-xwindowdump" + }, + + fileMimeType: function (path) { + utils.extensionToMimeType(utils.fileExtension(path)); + }, + + extensionToMimeType: function(extension) { + return utils.extensionMimeTypeMap[extension] || '*'; + }, isRelativeUrl: function(url) { var firstChar = url.slice(0, 1); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 13055ca852..600c6a2347 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -376,33 +376,26 @@ accountsController.accountSettings = function(req, res, next) { accountsController.uploadPicture = function (req, res, next) { var userPhoto = req.files.files[0]; var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; - - if (userPhoto.size > uploadSize * 1024) { - fs.unlink(userPhoto.path); - return next(new Error('[[error:file-too-big, ' + uploadSize + ']]')); - } - - var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; - if (allowedTypes.indexOf(userPhoto.type) === -1) { - fs.unlink(userPhoto.path); - return next(new Error('[[error:invalid-image-type, ' + allowedTypes.join(', ') + ']]')); - } - var extension = path.extname(userPhoto.name); - if (!extension) { - fs.unlink(userPhoto.path); - return next(new Error('[[error:invalid-image-extension]]')); - } - var updateUid = req.user ? req.user.uid : 0; var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; + var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1; async.waterfall([ + function(next) { + next(userPhoto.size > uploadSize * 1024 ? new Error('[[error:file-too-big, ' + uploadSize + ']]') : null); + }, + function(next) { + next(!extension ? new Error('[[error:invalid-image-extension]]') : null); + }, + function(next) { + file.isFileTypeAllowed(userPhoto.path, ['png', 'jpeg', 'jpg', 'gif'], next); + }, function(next) { image.resizeImage(userPhoto.path, extension, imageDimension, imageDimension, next); }, function(next) { - if (parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1) { + if (convertToPNG) { image.convertImageToPng(userPhoto.path, extension, next); } else { next(); @@ -412,7 +405,7 @@ accountsController.uploadPicture = function (req, res, next) { user.getUidByUserslug(req.params.userslug, next); }, function(uid, next) { - if(parseInt(updateUid, 10) === parseInt(uid, 10)) { + if (parseInt(updateUid, 10) === parseInt(uid, 10)) { return next(); } @@ -450,7 +443,6 @@ accountsController.uploadPicture = function (req, res, next) { return plugins.fireHook('filter:uploadImage', {image: userPhoto, uid: updateUid}, done); } - var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1; var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension); user.getUserField(updateUid, 'uploadedpicture', function (err, oldpicture) { @@ -524,7 +516,7 @@ accountsController.getChats = function(req, res, next) { if (!toUid || parseInt(toUid, 10) === parseInt(req.user.uid, 10)) { return helpers.notFound(req, res); } - + async.parallel({ toUser: async.apply(user.getUserFields, toUid, ['uid', 'username']), messages: async.apply(messaging.getMessages, req.user.uid, toUid, 'recent', false), diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index b7e590b6bf..dc0db0591d 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -7,6 +7,7 @@ var uploadsController = {}, async = require('async'), meta = require('../meta'), + file = require('../file'), plugins = require('../plugins'), utils = require('../../public/src/utils'), image = require('../image'); @@ -42,12 +43,18 @@ uploadsController.upload = function(req, res, filesIterator, next) { }; uploadsController.uploadPost = function(req, res, next) { - uploadsController.upload(req, res, function(file, next) { - if (file.type.match(/image./)) { - uploadImage(req.user.uid, file, next); - } else { - uploadFile(req.user.uid, file, next); - } + uploadsController.upload(req, res, function(uploadedFile, next) { + file.isFileTypeAllowed(uploadedFile.path, file.allowedExtensions(), function(err) { + if (err) { + return next(err); + } + + if (uploadedFile.type.match(/image./)) { + uploadImage(req.user.uid, uploadedFile, next); + } else { + uploadFile(req.user.uid, uploadedFile, next); + } + }); }, next); }; @@ -57,18 +64,24 @@ uploadsController.uploadThumb = function(req, res, next) { return next(new Error('[[error:topic-thumbnails-are-disabled]]')); } - uploadsController.upload(req, res, function(file, next) { - if(file.type.match(/image./)) { - var size = meta.config.topicThumbSize || 120; - image.resizeImage(file.path, path.extname(file.name), size, size, function(err) { - if (err) { - return next(err); - } - uploadImage(req.user.uid, file, next); - }); - } else { - next(new Error('[[error:invalid-file]]')); - } + uploadsController.upload(req, res, function(uploadedFile, next) { + file.isFileTypeAllowed(uploadedFile.path, file.allowedExtensions(), function(err) { + if (err) { + return next(err); + } + + if (uploadedFile.type.match(/image./)) { + var size = meta.config.topicThumbSize || 120; + image.resizeImage(uploadedFile.path, path.extname(uploadedFile.name), size, size, function(err) { + if (err) { + return next(err); + } + uploadImage(req.user.uid, uploadedFile, next); + }); + } else { + next(new Error('[[error:invalid-file]]')); + } + }); }, next); }; @@ -88,32 +101,32 @@ function uploadImage(uid, image, callback) { } } -function uploadFile(uid, file, callback) { +function uploadFile(uid, uploadedFile, callback) { if (plugins.hasListeners('filter:uploadFile')) { - return plugins.fireHook('filter:uploadFile', {file: file, uid: uid}, callback); + return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback); } if (parseInt(meta.config.allowFileUploads, 10) !== 1) { return callback(new Error('[[error:uploads-are-disabled]]')); } - if (!file) { + if (!uploadedFile) { return callback(new Error('[[error:invalid-file]]')); } - if (file.size > parseInt(meta.config.maximumFileSize, 10) * 1024) { + if (uploadedFile.size > parseInt(meta.config.maximumFileSize, 10) * 1024) { return callback(new Error('[[error:file-too-big, ' + meta.config.maximumFileSize + ']]')); } - var filename = 'upload-' + utils.generateUUID() + path.extname(file.name); - require('../file').saveFileToLocal(filename, 'files', file.path, function(err, upload) { + var filename = 'upload-' + utils.generateUUID() + path.extname(uploadedFile.name); + file.saveFileToLocal(filename, 'files', uploadedFile.path, function(err, upload) { if (err) { return callback(err); } callback(null, { url: upload.url, - name: file.name + name: uploadedFile.name }); }); } diff --git a/src/file.js b/src/file.js index 9433dfb40b..a3b594f326 100644 --- a/src/file.js +++ b/src/file.js @@ -3,7 +3,12 @@ var fs = require('fs'), nconf = require('nconf'), path = require('path'), - winston = require('winston'); + winston = require('winston'), + mmmagic = require('mmmagic'), + Magic = mmmagic.Magic, + mime = require('mime'), + + meta= require('./meta'); var file = {}; @@ -11,7 +16,7 @@ file.saveFileToLocal = function(filename, folder, tempPath, callback) { var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), folder, filename); - winston.info('Saving file '+ filename +' to : ' + uploadPath); + winston.verbose('Saving file '+ filename +' to : ' + uploadPath); var is = fs.createReadStream(tempPath); var os = fs.createWriteStream(uploadPath); @@ -30,4 +35,38 @@ file.saveFileToLocal = function(filename, folder, tempPath, callback) { is.pipe(os); }; +file.isFileTypeAllowed = function(path, allowedExtensions, callback) { + if (!Array.isArray(allowedExtensions) || !allowedExtensions.length) { + return callback(); + } + + allowedExtensions = allowedExtensions.filter(Boolean).map(function(extension) { + return extension.trim(); + }); + + var magic = new Magic(mmmagic.MAGIC_MIME_TYPE); + magic.detectFile(path, function(err, mimeType) { + if (err) { + return callback(err); + } + + var uploadedFileExtension = mime.extension(mimeType); + + if (allowedExtensions.indexOf(uploadedFileExtension) === -1) { + return callback(new Error('[[error:invalid-file-type, ' + allowedExtensions.join('-') + ']]')); + } + + callback(); + }); +}; + +file.allowedExtensions = function() { + var allowedExtensions = (meta.config.allowedFileExtensions || '').trim(); + if (!allowedExtensions) { + return []; + } + allowedExtensions = allowedExtensions.split(','); + return allowedExtensions; +}; + module.exports = file; \ No newline at end of file diff --git a/src/views/admin/settings/post.tpl b/src/views/admin/settings/post.tpl index 3cb55eac0e..98b99f3973 100644 --- a/src/views/admin/settings/post.tpl +++ b/src/views/admin/settings/post.tpl @@ -119,7 +119,10 @@ Allow users to upload topic thumbnails - Topic Thumb Size
+ Topic Thumb Size

+ + Allowed file types, (ie png, pdf, zip). Leave empty to allow all.

+