Permalink
Browse files

Refactored FileManager internals & behavior

+ Changed FileManager::expect() behavior & argument format
+ Allow restriction on mimeType
+ Allow restriction on maxFilesize
+ Add FileManager::removeFile(file, cb) => removes a file and unlinks from instance
+ Add FileManager::allow(mime1, mime2) => allows specific mimetypes (globally on instance)
+ Add FileManager::maxFilesize(size) => allows restriction on filesize (globally on instance)
+ Add FileManager::allowEmpty() => allows empty files for instance
  • Loading branch information...
1 parent 22ffda3 commit fecfe2852df583249ff2108588d4595d9549b6a8 @mendezcode mendezcode committed Sep 27, 2012
View
@@ -14,3 +14,5 @@
/test/fixtures/*skeleton/public/css/*.css
/test/fixtures/*skeleton/public/css/subdir/*.css
/test/fixtures/*skeleton/log/*.log
+/test/fixtures/*skeleton/incoming/*
+
@@ -1,7 +1,8 @@
-
+
/* File Manager */
var app = protos.app,
+ _ = require('underscore'),
fs = require('fs'),
slice = Array.prototype.slice;
@@ -15,40 +16,38 @@ function FileManager(files) {
return Object.keys(this.files);
});
+ Object.defineProperty(this, 'defaults', {
+ value: {
+ maxFilesize: 0,
+ mimeTypes: [],
+ noEmptyFiles: true
+ },
+ writable: true,
+ enumerable: false,
+ configurable: false
+ });
+
+ // Removed files
+ Object.defineProperty(this, 'removed', {
+ value: [],
+ writable: true,
+ enumerable: false,
+ configurable: false
+ });
+
// Instance files
Object.defineProperty(this, 'files', {
value: protos.extend({}, files),
writable: true,
enumerable: false,
- configurable: true
+ configurable: false
});
-
+
}
/**
- Expects files to match arguments. Other files not matching the
- ones listed in the expect arguments, will be removed silently,
- logging any errors encountered removing the files.
-
- You can define specific conditions for every file:
-
- a) 'filename': Expecting 'filename'. It's ok if it doens't exist.
-
- b) '*filename': File should be present. If the condition is not satisfied, the upload
- becomes invalid and all uploaded files are removed.
-
- c) '**filename': File should be present and it should not be empty. If the condition
- is not satisfied, the upload becomes invalid and all uploaded files are removed.
-
- Examples:
-
- if ( fm.expect('fileA', 'fileB', '*fileC', '**fileD') ) {
- fm.forEach(function(file) {
- res.json(file);
- });
- } else {
- res.end('400 Bad Request');
- }
+ Expects files to match arguments. Other files not matching the ones listed in the expect arguments,
+ will be removed silently, logging any errors encountered removing the files.
Any files that are not expected will be automatically removed as a security measure.
@@ -57,142 +56,176 @@ function FileManager(files) {
@public
*/
-FileManager.prototype.expect = function() {
- var expected = slice.call(arguments, 0);
+FileManager.prototype.expect = function(defs) {
- if (expected.length === 0) return true;
+ var log = function(err) {
+ if (err) (app.errorLog || app.log)(err);
+ }
- var files = this.files,
- emptyCheck = {},
- skip = [];
-
- // Detect which files to skip
- for (var file, o, max=expected.length, i=0; i < max; i++) {
- file = expected[i];
+ // Iterate over each definition
+ for (var file in defs) {
+
+ // Requires an object
+ var opts = defs[file];
- if (typeof file == 'object') {
- o = file;
- file = o.name;
- if (o.notEmpty) {
- file = '**' + file;
- } else if (o.required) {
- file = '*' + file;
- }
+ // Set maxfilesize
+ if (!opts.maxFilesize && this.defaults.maxFilesize) {
+ opts.maxFilesize = this.defaults.maxFilesize;
+ }
+
+ // Set mimetype
+ if (typeof opts.type == 'string') {
+ opts.type = [opts.type].concat(this.defaults.mimeTypes);
+ } else if (opts.type instanceof Array) {
+ opts.type = _.unique(opts.type.concat(this.defaults.mimeTypes));
+ } else {
+ opts.type = this.defaults.mimeTypes;
}
- if (file.charAt(0) == '*' ) {
- // File is required, don't accept uploaded files
- file = file.slice(1);
+ // Process file
+ if (file in this.files) {
- // Also check if file is not empty
- if (file.charAt(0) == '*') {
- file = file.slice(1);
- emptyCheck[file] = true;
+ // File present in uploads
+
+ var f = this.files[file];
+
+ // Remove empty files if present
+ if (this.defaults.noEmptyFiles) {
+ if (f.size === 0) {
+ this.removeFile(file);
+ continue;
+ }
}
- if (! (file in files) ) {
- this.removeAll(); // Invalidate upload, remove any uploaded files
- return false; // Exit function, Fail: file not present
- } else if (emptyCheck[file] && files[file].size === 0) {
- this.removeAll(); // Invalidate upload, remove any uploaded files
- return false; // Exit function, Fail: file is empty
- } else {
- skip.push(file); // Skip file from removal
+ // If Filesize restriction not met
+ if (opts.maxFilesize && f.size > opts.maxFilesize) {
+ this.removeFile(file);
+ continue;
}
- } else {
- // File is optional
- if (file in files) {
- skip.push(file); // Skip file from removal
+
+ // If Mimetype restriction not met
+ if (opts.type.length > 0 && opts.type.indexOf(f.type || f.mime) === -1) {
+ this.removeFile(file);
+ continue;
}
+
}
+
}
- // Logging function, reports any issues encountered removing files
- var log = function(err) {
- if (err) app.log(err);
- }
-
- // Remove any files not in skip
- for (file in files) {
- if (skip.indexOf(file) >= 0) continue;
- else {
- fs.unlink(files[file].path, log);
- delete files[file];
+ // Remove any files not in definitions
+ for (file in this.files) {
+ if (!(file in defs)) {
+ this.removeFile(file);
}
}
- return true; // success
-
+ return this.files;
+
}
/**
- Gets a specific file
+ Removes a file
+ @method removeFile
@param {string} file
- @return {object} file data
- @public
+ @return {object} instance
*/
-
-FileManager.prototype.get = function(file) {
- return this.files[file];
+
+FileManager.prototype.removeFile = function(file, callback) {
+ if (file in this.files) {
+ this.removed.push(file);
+ fs.unlink(this.files[file].path, callback || logCallback);
+ delete this.files[file];
+ }
+ return this;
}
/**
- Removes any empty files that have been uploaded
+ Sets the default maxFilesize configuration
- @public
+ @method maxFilesize
+ @param {int} size Filesize to expect in bytes
+ @return {object} instance
*/
-FileManager.prototype.removeEmpty = function() {
- var files = this.files;
-
- var log = function(err) {
- if (err) app.log(err);
- }
+FileManager.prototype.maxFilesize = function(size) {
+ this.defaults.maxFilesize = size;
+ return this;
+}
+
+/*
+ Sets the filemanager configuration to allow specific mime types
- for (var file in files) {
- if (files[file].size === 0) {
- fs.unlink(files[file].path, log);
- delete files[file];
- }
- }
+ @method allow
+ @param {str} Mime type to allow (multiple args)
+ @return {object} instance;
+ */
+
+FileManager.prototype.allow = function() {
+ var args = slice.call(arguments, 0);
+ this.defaults.mimeTypes = _.unique(this.defaults.mimeTypes.concat(args));
+ return this;
+}
+
+/*
+ Sets the filemanager configuration to allow empty files
+ @method allowEmpty
+ @return {object} instance;
+ */
+
+FileManager.prototype.allowEmpty = function() {
+ this.defaults.noEmptyFiles = false;
return this;
}
/**
- Removes all files uploaded
+ Gets a specific file
+ @method get
+ @param {string} file
+ @return {object} file data
@public
*/
+
+FileManager.prototype.get = function(file) {
+ return this.files[file] || null;
+}
-FileManager.prototype.removeAll = function() {
- var files = this.files;
-
- var log = function(err) {
- if (err) app.log(err);
- }
+/**
+ Removes all files uploaded
- for (var file in files) {
- fs.unlink(files[file].path, log);
- delete files[file];
+ @method removeAll
+ @param {function} callback Callback to pass to each fs.unlink call (optional)
+ @public
+ */
+
+FileManager.prototype.removeAll = function(callback) {
+ for (var file in this.files) {
+ this.removeFile(file, callback);
}
-
return this;
}
/**
Iterates over the files uploaded
+ @method forEach
@param {function} callback
@public
*/
FileManager.prototype.forEach = function(callback) {
var files = this.files;
for (var key in files) {
- callback.call(this, files[key]);
+ callback.call(this, key, files[key]);
}
+ return this.files;
+}
+
+function logCallback(err) {
+ if (err) (app.errorLog || app.log)(err);
}
module.exports = FileManager;
@@ -69,8 +69,11 @@ function TestController(app) {
var uploadCb;
post('/upload', uploadCb = function(req, res) {
req.getRequestData(function(fields, files) {
- if ( files.expect('**file') ) { // File should be present, and not empty
- var f = files.get('file');
+ files.expect({
+ file: {}
+ });
+ var f = files.get('file');
+ if ( f ) { // File should be present, and not empty
res.sendHeaders();
res.end(inspect(f));
files.removeAll();
Oops, something went wrong.

0 comments on commit fecfe28

Please sign in to comment.