Adding support for ACL, Content-Type, Max File Size and allowd Content-Types

This commit is contained in:
Benjamin Boudreau 2015-02-10 08:45:30 -05:00
parent 4ce675649e
commit 95baaff716
5 changed files with 118 additions and 24 deletions

BIN
example/largeImage.jpg Normal file

Binary file not shown.

View File

@ -1,6 +1,10 @@
var IncomingForm = require('formidable'); var IncomingForm = require('formidable');
var StringDecoder = require('string_decoder').StringDecoder; var StringDecoder = require('string_decoder').StringDecoder;
var defaultOptions = {
maxFileSize: 10 * 1024 * 1024 // 10 MB
};
/** /**
* Handle multipart/form-data upload to the storage service * Handle multipart/form-data upload to the storage service
* @param {Object} provider The storage service provider * @param {Object} provider The storage service provider
@ -16,6 +20,10 @@ exports.upload = function (provider, req, res, options, cb) {
options = {}; options = {};
} }
if (!options.maxFileSize) {
options.maxFileSize = defaultOptions.maxFileSize;
}
var form = new IncomingForm(this.options); var form = new IncomingForm(this.options);
container = options.container || req.params.container; container = options.container || req.params.container;
var fields = {}, files = {}; var fields = {}, files = {};
@ -56,18 +64,55 @@ exports.upload = function (provider, req, res, options, cb) {
type: part.mime type: part.mime
}; };
// Options for this file
// Build a filename
if ('function' === typeof options.getFilename) { if ('function' === typeof options.getFilename) {
file.name = options.getFilename(file, req, res); file.name = options.getFilename(file, req, res);
} }
self.emit('fileBegin', part.name, file); // Get allowed mime types
if (options.allowedContentTypes) {
var headers = {}; var allowedContentTypes;
if ('content-type' in part.headers) { if ('function' === typeof options.allowedContentTypes) {
headers['content-type'] = part.headers['content-type']; allowedContentTypes = options.allowedContentTypes(file, req, res);
} else {
allowedContentTypes = options.allowedContentTypes;
}
if (Array.isArray(allowedContentTypes) && allowedContentTypes.length !== 0) {
if (allowedContentTypes.indexOf(file.type) === -1) {
self._error(new Error('contentType "' + file.type + '" is not allowed (Must be in [' + allowedContentTypes.join(', ') + '])'));
return;
}
}
} }
var writer = provider.upload({container: container, remote: file.name}); // Get max file size
var maxFileSize;
if (options.maxFileSize) {
if ('function' === typeof options.maxFileSize) {
maxFileSize = options.maxFileSize(file, req, res);
} else {
maxFileSize = options.maxFileSize;
}
}
// Get access control list
if (options.acl) {
if ('function' === typeof options.acl) {
file.acl = options.acl(file, req, res);
} else {
file.acl = options.acl;
}
}
self.emit('fileBegin', part.name, file);
var uploadParams = {container: container, remote: file.name, contentType: file.type};
if (file.acl) {
uploadParams.acl = file.acl;
}
var writer = provider.upload(uploadParams);
var endFunc = function () { var endFunc = function () {
self._flushing--; self._flushing--;
@ -82,21 +127,19 @@ exports.upload = function (provider, req, res, options, cb) {
self._maybeEnd(); self._maybeEnd();
}; };
/* var fileSize = 0;
part.on('data', function (buffer) { if (maxFileSize) {
self.pause(); part.on('data', function (buffer) {
writer.write(buffer, function () { fileSize += buffer.length;
// pkgcloud stream doesn't make callbacks if (fileSize > maxFileSize) {
}); // We are missing some way to tell the provider to cancel upload/multipart upload of the current file.
self.resume(); // - s3-upload-stream doesn't provide a way to do this in it's public interface
}); // - We could call provider.delete file but it would not delete multipart data
self._error(new Error('maxFileSize exceeded, received ' + fileSize + ' bytes of field data (max is ' + maxFileSize + ')'));
part.on('end', function () { return;
}
writer.end(); // pkgcloud stream doesn't make callbacks });
endFunc(); }
});
*/
part.pipe(writer, { end: false }); part.pipe(writer, { end: false });
part.on("end", function () { part.on("end", function () {

View File

@ -27,9 +27,20 @@ function StorageService(options) {
} }
this.provider = options.provider; this.provider = options.provider;
this.client = factory.createClient(options); this.client = factory.createClient(options);
if ('function' === typeof options.getFilename) { if ('function' === typeof options.getFilename) {
this.getFilename = options.getFilename; this.getFilename = options.getFilename;
} }
if (options.acl) {
this.acl = options.acl;
}
if (options.allowedContentTypes) {
this.allowedContentTypes = options.allowedContentTypes;
}
if (options.maxFileSize) {
this.maxFileSize = options.maxFileSize;
}
} }
function map(obj) { function map(obj) {
@ -218,6 +229,15 @@ StorageService.prototype.upload = function(req, res, options, cb) {
if (this.getFilename && !options.getFilename) { if (this.getFilename && !options.getFilename) {
options.getFilename = this.getFilename; options.getFilename = this.getFilename;
} }
if (this.acl && !options.acl) {
options.acl = this.acl;
}
if (this.allowedContentTypes && !options.allowedContentTypes) {
options.allowedContentTypes = this.allowedContentTypes;
}
if (this.maxFileSize && !options.maxFileSize) {
options.maxFileSize = this.maxFileSize;
}
return handler.upload(this.client, req, res, options, cb); return handler.upload(this.client, req, res, options, cb);
}; };

View File

@ -1,2 +1,2 @@
test.jpg test.jpg
image-test.jpg image-*.jpg

View File

@ -15,7 +15,10 @@ var dsImage = loopback.createDataSource({
getFilename: function(fileInfo) { getFilename: function(fileInfo) {
return 'image-' + fileInfo.name; return 'image-' + fileInfo.name;
} },
acl: 'public-read',
allowedContentTypes: ['image/png', 'image/jpeg'],
maxFileSize: 5 * 1024 * 1024
}); });
var ImageContainer = dsImage.createModel('imageContainer'); var ImageContainer = dsImage.createModel('imageContainer');
@ -175,12 +178,40 @@ describe('storage service', function () {
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, function (err, res) { .expect(200, function (err, res) {
assert.deepEqual(res.body, {"result": {"files": {"image": [ assert.deepEqual(res.body, {"result": {"files": {"image": [
{"container": "album1", "name": "image-test.jpg", "type": "image/jpeg"} {"container": "album1", "name": "image-test.jpg", "type": "image/jpeg", "acl":"public-read"}
]}, "fields": {}}}); ]}, "fields": {}}});
done(); done();
}); });
}); });
it('uploads file wrong content type', function (done) {
request('http://localhost:3000')
.post('/imageContainers/album1/upload')
.attach('image', path.join(__dirname, '../example/app.js'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, function (err, res) {
assert(err);
assert(res.body.error.message.indexOf('is not allowed') !== -1);
done();
});
});
it('uploads file too large', function (done) {
request('http://localhost:3000')
.post('/imageContainers/album1/upload')
.attach('image', path.join(__dirname, '../example/largeImage.jpg'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, function (err, res) {
assert(err);
assert(res.body.error.message.indexOf('maxFileSize exceeded') !== -1);
done();
});
});
it('should get file by name', function (done) { it('should get file by name', function (done) {
request('http://localhost:3000') request('http://localhost:3000')