Adding support for ACL, Content-Type, Max File Size and allowd Content-Types
This commit is contained in:
parent
4ce675649e
commit
95baaff716
Binary file not shown.
|
@ -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 () {
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
test.jpg
|
test.jpg
|
||||||
image-test.jpg
|
image-*.jpg
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue