From b74894efdb03b3877388f03c6132dd901546063d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Francisco=20Santos?= Date: Fri, 5 Aug 2022 17:18:04 +0200 Subject: [PATCH] feature: add user defined metadata and content disposition to post policy (#1036) --- docs/API.md | 8 ++++ docs/zh_CN/API.md | 8 ++++ examples/presigned-postpolicy.js | 8 ++++ src/main/minio.js | 23 ++++++++- src/test/functional/functional-tests.js | 63 +++++++++++++++++++++++++ src/test/unit/test.js | 32 +++++++++++++ 6 files changed, 141 insertions(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index e3513653..d64ed38f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1912,8 +1912,16 @@ policy.setExpires(expires) // Only allow 'text'. policy.setContentType('text/plain') +// Set content disposition response header. +policy.setContentDisposition('attachment; filename=text.txt') + // Only allow content size in range 1KB to 1MB. policy.setContentLengthRange(1024, 1024*1024) + +// Set key-value user defined metadata +policy.setUserMetaData({ + key: 'value' +}) ``` POST your content from the browser using `superagent`: diff --git a/docs/zh_CN/API.md b/docs/zh_CN/API.md index 8b9f8b0a..84f1f46f 100644 --- a/docs/zh_CN/API.md +++ b/docs/zh_CN/API.md @@ -817,8 +817,16 @@ policy.setExpires(expires) // Only allow 'text'. policy.setContentType('text/plain') +// Set content disposition response header. +policy.setContentDisposition('attachment; filename=text.txt') + // Only allow content size in range 1KB to 1MB. policy.setContentLengthRange(1024, 1024*1024) + +// Set key-value user defined metadata +policy.setUserMetaData({ + key: 'value' +}) ``` 使用`superagent`通过浏览器POST你的数据: diff --git a/examples/presigned-postpolicy.js b/examples/presigned-postpolicy.js index 3c44c9f2..7e56df4b 100644 --- a/examples/presigned-postpolicy.js +++ b/examples/presigned-postpolicy.js @@ -39,6 +39,14 @@ policy.setExpires(expires) policy.setContentLengthRange(1024, 1024*1024) // Min upload length is 1KB Max upload size is 1MB +policy.setContentType('text/plain') + +policy.setContentDisposition('attachment; filename=text.txt') + +policy.setUserMetaData({ + key: 'value' +}) + s3Client.presignedPostPolicy(policy, function(e, data) { if (e) return console.log(e) var curl = [] diff --git a/src/main/minio.js b/src/main/minio.js index 92041f52..a8dbf16d 100644 --- a/src/main/minio.js +++ b/src/main/minio.js @@ -1951,7 +1951,7 @@ export class Client { // presignedPostPolicy can be used in situations where we want more control on the upload than what // presignedPutObject() provides. i.e Using presignedPostPolicy we will be able to put policy restrictions - // on the object's `name` `bucket` `expiry` `Content-Type` + // on the object's `name` `bucket` `expiry` `Content-Type` `Content-Disposition` `metaData` presignedPostPolicy(postPolicy, cb) { if (this.anonymous) { throw new errors.AnonymousRequestError('Presigned POST policy cannot be generated for anonymous requests') @@ -3674,6 +3674,15 @@ export class PostPolicy { this.formData['Content-Type'] = prefix } + // set Content-Disposition + setContentDisposition(value) { + if (!value) { + throw new Error('content-disposition cannot be null') + } + this.policy.conditions.push(['eq', '$Content-Disposition', value]) + this.formData['Content-Disposition'] = value + } + // set minimum/maximum length of what Content-Length can be. setContentLengthRange(min, max) { if (min > max) { @@ -3687,6 +3696,18 @@ export class PostPolicy { } this.policy.conditions.push(['content-length-range', min, max]) } + + // set user defined metadata + setUserMetaData(metaData) { + if (!isObject(metaData)) { + throw new TypeError('metadata should be of type "object"') + } + Object.entries(metaData).forEach(([key, value]) => { + const amzMetaDataKey = `x-amz-meta-${key}` + this.policy.conditions.push(['eq', `$${amzMetaDataKey}`, value]) + this.formData[amzMetaDataKey] = value + }) + } } export * from './notification' diff --git a/src/test/functional/functional-tests.js b/src/test/functional/functional-tests.js index bfcae27d..2925226e 100644 --- a/src/test/functional/functional-tests.js +++ b/src/test/functional/functional-tests.js @@ -1101,6 +1101,69 @@ describe('functional tests', function () { }) }) + step('presignedPostPolicy(postPolicy, cb)_postPolicy:setContentDisposition_inline', done => { + var policy = client.newPostPolicy() + var objectName = 'test-content-disposition' + uuid.v4() + policy.setKey(objectName) + policy.setBucket(bucketName) + policy.setContentDisposition('inline') + + client.presignedPostPolicy(policy, (e, data) => { + if (e) return done(e) + var req = superagent.post(data.postURL) + _.each(data.formData, (value, key) => req.field(key, value)) + req.attach('file', Buffer.from([_1byte]), 'test') + req.end(function(e) { + if (e) return done(e) + client.removeObject(bucketName, objectName, done) + }) + req.on('error', e => done(e)) + }) + }) + + step('presignedPostPolicy(postPolicy, cb)_postPolicy:setContentDisposition_attachment', done => { + var policy = client.newPostPolicy() + var objectName = 'test-content-disposition' + uuid.v4() + policy.setKey(objectName) + policy.setBucket(bucketName) + policy.setContentDisposition('attachment; filename= My* Docume! nt.json') + + client.presignedPostPolicy(policy, (e, data) => { + if (e) return done(e) + var req = superagent.post(data.postURL) + _.each(data.formData, (value, key) => req.field(key, value)) + req.attach('file', Buffer.from([_1byte]), 'test') + req.end(function (e) { + if (e) return done(e) + client.removeObject(bucketName, objectName, done) + }) + req.on('error', e => done(e)) + }) + }) + + step('presignedPostPolicy(postPolicy, cb)_postPolicy:setUserMetaData_', done => { + var policy = client.newPostPolicy() + var objectName = 'test-metadata' + uuid.v4() + policy.setKey(objectName) + policy.setBucket(bucketName) + policy.setUserMetaData({ + key: 'my-value', + anotherKey: 'another-value' + }) + + client.presignedPostPolicy(policy, (e, data) => { + if (e) return done(e) + var req = superagent.post(data.postURL) + _.each(data.formData, (value, key) => req.field(key, value)) + req.attach('file', Buffer.from([_1byte]), 'test') + req.end(function(e) { + if (e) return done(e) + client.removeObject(bucketName, objectName, done) + }) + req.on('error', e => done(e)) + }) + }) + step('presignedPostPolicy(postPolicy)_postPolicy: null_', done => { client.presignedPostPolicy(null) .then(() => { diff --git a/src/test/unit/test.js b/src/test/unit/test.js index 05889572..c91d3f17 100644 --- a/src/test/unit/test.js +++ b/src/test/unit/test.js @@ -376,6 +376,38 @@ describe('Client', function() { } }) }) + describe('presigned-post-policy', () => { + it('should not generate content type for undefined value', () => { + assert.throws(() => { + var policy = client.newPostPolicy() + policy.setContentType() + }, /content-type cannot be null/) + }) + it('should not generate content disposition for undefined value', () => { + assert.throws(() => { + var policy = client.newPostPolicy() + policy.setContentDisposition() + }, /content-disposition cannot be null/) + }) + it('should not generate user defined metadata for string value', () => { + assert.throws(() => { + var policy = client.newPostPolicy() + policy.setUserMetaData('123') + }, /metadata should be of type "object"/) + }) + it('should not generate user defined metadata for null value', () => { + assert.throws(() => { + var policy = client.newPostPolicy() + policy.setUserMetaData(null) + }, /metadata should be of type "object"/) + }) + it('should not generate user defined metadata for undefined value', () => { + assert.throws(() => { + var policy = client.newPostPolicy() + policy.setUserMetaData() + }, /metadata should be of type "object"/) + }) + }) }) describe('User Agent', () => { it('should have a default user agent', () => {