diff --git a/package-lock.json b/package-lock.json index d61ff409..6fe225b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@aws-sdk/lib-storage": "3.515.0", "@aws-sdk/s3-request-presigner": "3.421.0", "@fastify/accepts": "^4.3.0", - "@fastify/multipart": "^7.6.0", + "@fastify/multipart": "^8.3.0", "@fastify/rate-limit": "^7.6.0", "@fastify/swagger": "^8.3.1", "@fastify/swagger-ui": "^1.7.0", @@ -2428,14 +2428,11 @@ } }, "node_modules/@fastify/busboy": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", - "integrity": "sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==", - "dependencies": { - "text-decoding": "^1.0.0" - }, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "engines": { - "node": ">=10.17.0" + "node": ">=14" } }, "node_modules/@fastify/deepmerge": { @@ -2457,16 +2454,14 @@ } }, "node_modules/@fastify/multipart": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-7.6.0.tgz", - "integrity": "sha512-mQoz0SMxk1WAYLYvQypoj3GYhCbmhnFl2LdleTzKQ4HgPfhVcMT014uln2wgzRh2y+gL74b/+j0mMVgvOKpqpQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.3.0.tgz", + "integrity": "sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==", "dependencies": { - "@fastify/busboy": "^1.0.0", + "@fastify/busboy": "^2.1.0", "@fastify/deepmerge": "^1.0.0", "@fastify/error": "^3.0.0", - "end-of-stream": "^1.4.4", "fastify-plugin": "^4.0.0", - "hexoid": "^1.0.0", "secure-json-parse": "^2.4.0", "stream-wormhole": "^1.1.0" } @@ -7728,14 +7723,6 @@ "node": ">= 0.4" } }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "engines": { - "node": ">=8" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10934,11 +10921,6 @@ "node": ">=8" } }, - "node_modules/text-decoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", - "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13443,12 +13425,9 @@ } }, "@fastify/busboy": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", - "integrity": "sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==", - "requires": { - "text-decoding": "^1.0.0" - } + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" }, "@fastify/deepmerge": { "version": "1.3.0", @@ -13469,16 +13448,14 @@ } }, "@fastify/multipart": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-7.6.0.tgz", - "integrity": "sha512-mQoz0SMxk1WAYLYvQypoj3GYhCbmhnFl2LdleTzKQ4HgPfhVcMT014uln2wgzRh2y+gL74b/+j0mMVgvOKpqpQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.3.0.tgz", + "integrity": "sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==", "requires": { - "@fastify/busboy": "^1.0.0", + "@fastify/busboy": "^2.1.0", "@fastify/deepmerge": "^1.0.0", "@fastify/error": "^3.0.0", - "end-of-stream": "^1.4.4", "fastify-plugin": "^4.0.0", - "hexoid": "^1.0.0", "secure-json-parse": "^2.4.0", "stream-wormhole": "^1.1.0" } @@ -17451,11 +17428,6 @@ "function-bind": "^1.1.2" } }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -19862,11 +19834,6 @@ "minimatch": "^3.0.4" } }, - "text-decoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", - "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 76c32fb6..cadfb705 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@aws-sdk/lib-storage": "3.515.0", "@aws-sdk/s3-request-presigner": "3.421.0", "@fastify/accepts": "^4.3.0", - "@fastify/multipart": "^7.6.0", + "@fastify/multipart": "^8.3.0", "@fastify/rate-limit": "^7.6.0", "@fastify/swagger": "^8.3.1", "@fastify/swagger-ui": "^1.7.0", diff --git a/src/storage/uploader.ts b/src/storage/uploader.ts index 2b07ef07..e17226b7 100644 --- a/src/storage/uploader.ts +++ b/src/storage/uploader.ts @@ -326,12 +326,9 @@ export class Uploader { const customMd = request.headers['x-metadata'] if (typeof customMd === 'string') { - if (userMetadata && Buffer.byteLength(customMd, 'utf8') > MAX_CUSTOM_METADATA_SIZE) { - throw ERRORS.EntityTooLarge(undefined, 'metadata') - } - try { - userMetadata = JSON.parse(customMd) + const json = Buffer.from(customMd, 'base64').toString('utf8') + userMetadata = JSON.parse(json) } catch (e) { // no-op } diff --git a/src/test/object.test.ts b/src/test/object.test.ts index c1958c73..72aee6f2 100644 --- a/src/test/object.test.ts +++ b/src/test/object.test.ts @@ -354,7 +354,7 @@ describe('testing POST object via multipart upload', () => { expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() }) - test('successfully uploading an object with custom metadata', async () => { + test('successfully uploading an object with custom metadata using form data', async () => { const form = new FormData() form.append('file', fs.createReadStream(`./src/test/assets/sadcat.jpg`)) form.append( @@ -395,6 +395,45 @@ describe('testing POST object via multipart upload', () => { }) }) + test('successfully uploading an object with custom metadata using stream', async () => { + const file = fs.createReadStream(`./src/test/assets/sadcat.jpg`) + + const headers = { + authorization: `Bearer ${serviceKey}`, + 'x-upsert': 'true', + 'x-metadata': Buffer.from( + JSON.stringify({ + test1: 'test1', + test2: 'test2', + }) + ).toString('base64'), + } + + const response = await app().inject({ + method: 'POST', + url: '/object/bucket2/sadcat-upload3018.png', + headers, + payload: file, + }) + expect(response.statusCode).toBe(200) + expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() + + const client = await getSuperuserPostgrestClient() + + const object = await client + .table('objects') + .select('*') + .where('name', 'sadcat-upload3018.png') + .where('bucket_id', 'bucket2') + .first() + + expect(object).not.toBeFalsy() + expect(object?.user_metadata).toEqual({ + test1: 'test1', + test2: 'test2', + }) + }) + test('fetch object metadata', async () => { const form = new FormData() form.append('file', fs.createReadStream(`./src/test/assets/sadcat.jpg`)) @@ -2121,7 +2160,7 @@ describe('testing list objects', () => { }) expect(response.statusCode).toBe(200) const responseJSON = JSON.parse(response.body) - expect(responseJSON).toHaveLength(8) + expect(responseJSON).toHaveLength(9) const names = responseJSON.map((ele: any) => ele.name) expect(names).toContain('curlimage.jpg') expect(names).toContain('private')